使用 R 和 Gephi 理解 Twitter 动态:文本分析和中心性
已发表: 2022-07-22本文扩展并深化了我们社交网络分析系列的第一部分中提出的分析。 我们使用第一篇文章中构建的相同 Twitter 数据集和交互网络。 但是,这一次的想法是更好地推断主要参与者,确定他们的讨论主题,并了解这些主题是如何传播的。
社交网络中心性
为了实现我们的目标,首先我们需要引入中心性的概念。 在网络科学中,中心性是指对网络有强烈影响的节点。 影响是一个模棱两可的概念; 它可以从很多方面来理解。 具有许多边的节点是否比具有较少但更“重要”边的节点更有影响力? 什么构成了社交网络的重要优势?
为了解决这些模糊性,网络科学家开发了许多中心性度量。 在这里,我们讨论了四种常用的测量方法,但还有更多可用的测量方法。
程度
最常见和最直观的衡量标准是度中心性。 度中心性背后的想法很简单:通过节点的度来衡量影响。 如果图是有向的,它可以有变体; 在这种情况下,您可以测量入度和出度——第一个称为中心分数,第二个称为权威分数。
在本系列的第一部分中,我们使用了无向方法。 这一次,我们专注于入度方法。 这允许通过强调被其他人转发的用户而不是仅频繁转发的用户来进行更准确的分析。
特征向量
特征向量测量建立在度中心性之上。 有影响力的节点越多地指向给定节点,其得分就越高。 我们从邻接矩阵开始,其中行和列代表节点,我们使用 1 或 0 来表示给定行和列的对应节点是否连接。 主要计算估计矩阵的特征向量。 主特征向量将包含我们想要的中心度度量,其中位置i将保持节点i的中心度得分。
网页排名
PageRank 是谷歌核心特征向量度量的变体。 谷歌使用的确切方法是未知的,但一般的想法是每个节点从分数 1 开始,然后将其分数等分分配到它的每条边上。 例如,如果一个节点有三个从它延伸出来的边,它会通过每条边“发送”三分之一的分数。 同时,指向它的边使节点变得更加重要。 这导致了一个具有N个未知数的N个方程的可解系统。
介数
第四个度量,介数,使用了一种非常不同的方法。 在这里,如果一个节点包含在其他节点之间的许多短路径中,则称该节点具有影响力。 也就是说,它负责与许多其他节点进行通信,连接“不同的世界”。
例如,在社交网络分析中,这类节点可以理解为帮助他人找到新工作或建立新联系的人——它们是通往未知社交圈的大门。
我应该使用哪个?
适当的中心性度量取决于您的分析目标。 你想知道哪些用户在数量上经常被别人挑出来吗? 学位中心性可能是您的最佳选择。 还是您更喜欢考虑质量的中心性度量? 在这种情况下,特征向量或 PageRank 会产生更好的结果。 如果您想知道哪些用户最有效地充当不同社区之间的桥梁,那么介数是您的最佳选择。
当使用多个相似的度量时,例如,特征向量和 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 个最中心的用户:
程度 |
|
特征向量 |
|
网页排名 |
|
介数 |
|
结果:
程度 | 特征向量 | 网页排名 | 介数 | ||||
---|---|---|---|---|---|---|---|
ESPNFC | 5892 | PSG_inside | 1 | 蒙多达博拉 | 0.037 | 视点 | 77704 |
巨魔足球 | 5755 | 船员垫19 | 0.51 | 阿莱利帕罗蒂 | 0.026 | 爱德蒙奥里斯 | 76425 |
PSG_inside | 5194 | eh01195991 | 0.4 | PSG_inside | 0.017 | 巴*****拉 | 63799 |
船员垫19 | 4344 | 穆罕默德135680 | 0.37 | 罗伊内默 | 0.016 | 弗朗西斯科盖乌斯 | 63081 |
br足球 | 4054 | ActuFoot_ | 0.34 | 巨魔足球 | 0.013 | 耶米哈赞 | 62534 |
PSG_espanol | 3616 | 马特瓦尔 | 0.34 | ESPNFC | 0.01 | hashtag2weet | 61123 |
IbaiOut | 3258 | ESPNFC | 0.3 | PSG_espanol | 0.007 | 安吉拉_FCB | 60991 |
ActuFoot_ | 3175 | br足球 | 0.25 | 瞬间足 | 0.007 | Zyyon_ | 57269 |
足球幽默 | 2976 | 赛勒月军 | 0.22 | IbaiOut | 0.006 | 船员垫19 | 53758 |
蒙多达博拉 | 2778 | 约翰斯维尔帕特 | 0.2 | 2010芯片先生 | 0.006 | 奥拉瓦莱 | 49572 |
我们可以看到前三个度量共享多个用户,例如 PSG_inside、ESPNFC、CrewsMat19 和 TrollFootball。 我们可以假设他们对讨论有很大的影响。 中介性有一种不同的方法来衡量中心性,因此与其他技术没有太多重叠。
注:本文提及的推特账号所表达的观点不代表Toptal或作者的观点。
在下图中,您可以看到我们原始的带有两个用户标签叠加层的彩色网络图。 在第一个中,节点通过它们的 PageRank 分数突出显示,在第二个中,通过它们的中介分数突出显示:
Gephi 可用于重现这些图像。 您可以使用统计面板中的 Network Diameter 按钮估计介数或 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) 。 但是,我们怎么能事先知道呢? 一种常见的方法是在不同的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
下表详细列出了检测到的五个最重要的主题以及举例说明它们的 10 个主要词:
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | 梅西 | 梅西 | 梅西 | 梅西 | 梅西 |
2 | 莱昂内尔 | 联盟 | 美东时间 | psg | |
3 | 莱昂内尔·梅西 | 邮政 | 赢 | 我 | 狮子座 |
4 | psg | 百万 | 目标 | 澳大利亚 | leo_messi |
5 | 马德里 | 喜欢 | ch | 倒 | 阿霍拉 |
6 | 真实的 | 斯波 | 离子 | 帕斯 | 康帕 |
7 | 巴塞罗那 | 山羊 | ch_ions | avec | va |
8 | 巴黎 | psg | 伦敦大学学院 | 杜 | SER |
9 | 皇家马德里 | 酒吧 | 气球 | 奎 | 斗牛士 |
10 | mbapp | 大 | 世界 | 杰 | 主要 |
尽管该社区中的大多数用户都是说英语的,但仍然有一些说法语和西班牙语的用户(表中的 t_4 和 t_5)。 我们可以推断,第一个话题与梅西之前的球队(巴塞罗那足球俱乐部)有关,第二个话题是关于梅西在 Instagram 上的帖子,第三个话题关注的是梅西的成就。
现在我们有了主题,我们可以预测其中哪些是讨论最多的。 为此,我们将首先连接用户的推文(同样,来自最大的社区):
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 | |
___亚历克斯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 绘制它,以及检测社区和重要用户。 在本期中,我们通过演示使用其他标准来检测有影响力的用户来扩展此分析。 我们还演示了如何检测和推断用户正在谈论的内容并在网络中绘制出来。
在我们的下一篇文章中,我们将通过展示用户如何检测社交媒体的祸害:垃圾邮件机器人和巨魔来继续深化这一分析。
进一步阅读 Toptal 工程博客:
- 使用 Power BI 和 R 进行社交网络分析:自定义视觉效果指南