فهم ديناميكيات Twitter باستخدام R و Gephi: تحليل النص والمركزية
نشرت: 2022-07-22توسع هذه المقالة وتعمق التحليل المقدم في الدفعة الأولى من سلسلة تحليل الشبكات الاجتماعية الخاصة بنا. نستخدم نفس مجموعة بيانات Twitter وشبكة التفاعل التي تم إنشاؤها في المقالة الأولى. ومع ذلك ، فإن الفكرة هذه المرة هي استنتاج الجهات الفاعلة الرئيسية بشكل أفضل ، وتحديد موضوعات المناقشة الخاصة بهم ، وفهم كيفية انتشار هذه الموضوعات.
مركزية الشبكة الاجتماعية
لتحقيق أهدافنا ، نحتاج أولاً إلى تقديم مفهوم المركزية . في علم الشبكات ، تشير المركزية إلى العقد التي لها تأثير قوي على الشبكة. التأثير مفهوم غامض. يمكن فهمه بعدة طرق. هل العقدة ذات الحواف المتعددة أكثر تأثيرًا من العقدة ذات الحواف الأقل ولكن الأكثر "أهمية"؟ ما الذي يشكل ميزة مهمة على الشبكة الاجتماعية؟
لمعالجة هذه الالتباسات ، طور علماء الشبكات العديد من مقاييس المركزية. هنا ، نناقش أربعة مقاييس شائعة الاستخدام ، على الرغم من توفر المزيد منها.
درجة
المقياس الأكثر شيوعًا وبديهية هو مركزية الدرجة. الفكرة الكامنة وراء مركزية الدرجة بسيطة: قم بقياس التأثير بواسطة درجة العقدة. يمكن أن يكون لها متغيرات إذا تم توجيه الرسم البياني ؛ في هذه الحالة ، يمكنك قياس الدرجة المستقلة والدرجة الخارجية — يُعرف الأول باسم درجة المحور والثاني باسم درجة السلطة.
في الجزء الأول من هذه السلسلة ، استخدمنا الطريقة غير الموجهة. هذه المرة ، نركز على النهج المستقل. يتيح ذلك تحليلًا أكثر دقة من خلال التأكيد على المستخدمين الذين أعادوا تغريدهم من قبل الآخرين على المستخدمين الذين يعيدون تغريدهم بشكل متكرر.
Eigenvector
يعتمد مقياس eigenvector على مركزية الدرجة. وكلما زادت العقد المؤثرة التي تشير إلى عقدة معينة ، زادت نقاطها. نبدأ بمصفوفة متجاورة ، حيث تمثل الصفوف والأعمدة العقد ، ونستخدم 1 أو 0 للإشارة إلى ما إذا كانت العقد المقابلة لصف وعمود معينين متصلة أم لا. يقدّر الحساب الرئيسي المتجهات الذاتية للمصفوفة. سيحتوي ناقل eigenvector الرئيسي على مقاييس المركزية التي نريدها ، حيث سيحتفظ الموضع i بدرجة مركزية العقدة i .
رتبة صفحة
PageRank هو تباين مقياس eigenvector في صميم Google. الطريقة الدقيقة التي يستخدمها Google غير معروفة ، ولكن الفكرة العامة هي أن كل عقدة تبدأ بعلامة 1 ، ثم توزع نتيجتها في أجزاء متساوية على كل من حوافها. على سبيل المثال ، إذا كانت العقدة لها ثلاثة حواف تمتد منها ، فإنها "ترسل" ثلث نقاطها عبر كل حافة. في الوقت نفسه ، تصبح العقدة أكثر أهمية من خلال الحواف التي تشير إليها. ينتج عن هذا نظام قابل للحل من معادلات N مع N مجهولة.
بين
المقياس الرابع ، البينية ، يستخدم نهجًا مختلفًا تمامًا. هنا ، يُقال أن العقدة مؤثرة إذا تم تضمينها في العديد من المسارات القصيرة بين العقد الأخرى. أي أنها مسؤولة عن التواصل مع العديد من العقد الأخرى ، وربط "عوالم مختلفة".
على سبيل المثال ، في تحليل الشبكات الاجتماعية ، يمكن فهم هذه الأنواع من العقد على أنها أنواع الأشخاص الذين يساعدون الآخرين في العثور على وظائف جديدة أو إنشاء روابط جديدة - فهم الأبواب لدوائر اجتماعية لم تكن معروفة من قبل.
ما الذي يجب علي استخدامه؟
يعتمد مقياس المركزية المناسب على هدف تحليلك. هل تريد معرفة المستخدمين الذين يتم تحديدهم بشكل متكرر من قبل الآخرين من حيث الكمية؟ من المحتمل أن تكون مركزية الدرجة هي خيارك الأفضل. أم تفضل مقياس مركزية يأخذ بعين الاعتبار الجودة؟ في هذه الحالة ، سيعطي eigenvector أو 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 مستخدمين مركزيين حسب كل مقياس:
درجة | |
Eigenvector | |
رتبة صفحة | |
بين | |
النتائج:
درجة | Eigenvector | رتبة صفحة | بين | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | داخل PSG | 1 | مونودابولا | 0.037 | الآراء | 77704 |
ترول كرة القدم | 5755 | الطاقم | 0.51 | AleLiparoti | 0.026 | ادموند اوريس | 76425 |
داخل PSG | 5194 | eh01195991 | 0.4 | داخل PSG | 0.017 | با ***** لا | 63799 |
الطاقم | 4344 | محمد 135680 | 0.37 | روي نمر | 0.016 | فرانسيسكو جايوس | 63081 |
brfootball | 4054 | أكتوفوت | 0.34 | ترول كرة القدم | 0.013 | يميحزان | 62534 |
PSG_espanol | 3616 | مارتفال | 0.34 | ESPNFC | 0.01 | hashtag2weet | 61123 |
IbaiOut | 3258 | ESPNFC | 0.3 | PSG_espanol | 0.007 | Angela_FCB | 60991 |
أكتوفوت | 3175 | brfootball | 0.25 | lnstantFoot | 0.007 | زيون | 57269 |
فوتيهومور | 2976 | SaylorMoonArmy | 0.22 | IbaiOut | 0.006 | الطاقم | 53758 |
مونودابولا | 2778 | JohnsvillPat | 0.2 | 2010MisterChip | 0.006 | مدين أولوالي | 49572 |
يمكننا أن نرى أن المقاييس الثلاثة الأولى تشترك في عدد من المستخدمين ، مثل PSG_inside و ESPNFC و CrewsMat19 و TrollFootball. يمكننا أن نفترض أن لديهم تأثيرًا قويًا على المناقشة. تتميز البينية بمقاربة مختلفة لقياس المركزية ، وبالتالي لا تظهر قدرًا كبيرًا من التداخل مع الأساليب الأخرى.
ملاحظة: الآراء التي عبرت عنها حسابات تويتر المذكورة في هذا المقال لا تعكس آراء Toptal أو المؤلف.
في الصور التالية ، يمكنك مشاهدة الرسم البياني الأصلي الملون للشبكة مع تراكبتين لتسمية المستخدم. في الأول ، يتم تمييز العقد من خلال درجات PageRank الخاصة بها ، وفي الثانية ، من خلال نتائجها بين الدرجات:
يمكن استخدام Gephi لإعادة إنتاج هذه الصور. يمكنك تقدير الدرجات البينية أو PageRank باستخدام زر قطر الشبكة في لوحة الإحصائيات. بعد ذلك ، يمكنك إظهار أسماء العقد باستخدام السمات كما هو موضح في الدفعة الأولى من هذه السلسلة.
تحليل النص: R و LDA
يمكننا أيضًا تحليل مناقشات الشبكات الاجتماعية لتحديد ما يتحدث عنه المستخدمون. هناك طرق متعددة للتعامل مع هذا. سنقوم بعمل نمذجة للموضوعات من خلال تخصيص Latent Dirichlet Allocation (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) الموضوعات التي نريد تقديرها. ومع ذلك ، كيف يمكننا معرفة ذلك مسبقًا؟ يتمثل أحد الأساليب الشائعة في تدريب نماذج LDA على قيم k المختلفة وقياس تماسك كل منها. سنفعل ذلك لقيم k من 3 حتى 20 ، نظرًا لأن القيم خارج هذا النطاق لا تستحق التحقق ، في تجربتي:
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 ، لذلك سنستخدم نموذج LDA المدرب على 13 موضوعًا. من خلال وظيفة GetTopTerms ، يمكننا رؤية الكلمات الرئيسية العشر لكل موضوع وتقدير دلالات الموضوع من خلالها:
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
يوضح الجدول التالي أهم خمسة موضوعات تم الكشف عنها والكلمات الرئيسية العشر التي تمثلها:
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | ميسي | ميسي | ميسي | ميسي | ميسي |
2 | ليونيل | الانستغرام | الدوري | EST | psg |
3 | ليونيل ميسي | بريد | فوز | انا | ليو |
4 | psg | مليون | الأهداف | au | ليو ميسي |
5 | مدريد | الإعجابات | الفصل | يصب | أهورا |
6 | حقا | spo | الأيونات | باس | كومبا |
7 | برشلونة | ماعز | ch_ions | افيك | va |
8 | باريس | psg | ucl | دو | سر |
9 | ريال مدريد | شريط | بالون | كوي | جوجادور |
10 | mbapp | أكبر | العالمية | جي | ميجور |
على الرغم من أن معظم المستخدمين في هذا المجتمع هم من المتحدثين باللغة الإنجليزية ، لا يزال هناك عدد من المتحدثين بالفرنسية والإسبانية (t_4 و t_5 في الجدول). يمكننا أن نستنتج أن الموضوع الأول يتعلق بفريق ميسي السابق (برشلونة) ، والموضوع الثاني حول منشور ميسي على إنستغرام ، والموضوع الثالث يركز على إنجازات ميسي.
الآن بعد أن أصبح لدينا الموضوعات ، يمكننا التنبؤ بأي منها كان الأكثر مناقشة. للقيام بذلك ، سنقوم أولاً بتجميع تغريدات المستخدمين (مرة أخرى ، من أكبر مجتمع):
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). بعد ذلك ، نسمي وظيفة predict
باستخدام نموذج LDA الخاص بنا و DTM كوسيطات. أيضًا ، قمنا بتعيين الطريقة على 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 | |
___ أليكس 1 | 0.00952380 | 0.00952380 | 0.00952380 | 0.00952380 | 0.00952380 | |
[...] |
أخيرًا ، باستخدام هذه المعلومات ، يمكننا إنشاء سمة جديدة على الرسم البياني للعقدة لتحديد الموضوع الذي تم التحدث عنه أكثر من قبل أي مستخدم. ثم يمكننا إنشاء ملف GML جديد لتصوره في Gephi:
# 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 ، واكتشاف المجتمعات والمستخدمين المهمين. في هذه الحلقة ، قمنا بتوسيع هذا التحليل من خلال توضيح استخدام معايير إضافية للكشف عن المستخدمين المؤثرين. لقد أوضحنا أيضًا كيفية اكتشاف واستنتاج ما كان يتحدث عنه المستخدمون ورسم ذلك في الشبكة.
في مقالتنا التالية ، سنواصل تعميق هذا التحليل من خلال إظهار كيف يمكن للمستخدمين اكتشاف بلاء وسائل التواصل الاجتماعي: spambots و trolls.
مزيد من القراءة على مدونة Toptal Engineering:
- تحليل الشبكة الاجتماعية باستخدام Power BI و R: دليل مرئي مخصص