R 및 Gephi를 통한 Twitter 역학 이해: 텍스트 분석 및 중심성
게시 됨: 2022-07-22이 기사는 소셜 네트워크 분석 시리즈의 첫 번째 기사에서 제시된 분석을 확장하고 심화합니다. 첫 번째 기사에서 구성한 것과 동일한 Twitter 데이터 세트와 상호 작용 네트워크를 사용합니다. 그러나 이번에는 주역을 더 잘 추론하고 토론 주제를 식별하고 이러한 주제가 어떻게 확산되는지 이해하는 것이 아이디어입니다.
소셜 네트워크 중심성
목표를 달성하려면 먼저 중심성 개념을 도입해야 합니다. 네트워크 과학에서 중심성은 네트워크에 강한 영향을 미치는 노드를 나타냅니다. 영향력은 모호한 개념입니다. 그것은 여러 가지로 이해할 수 있다. 많은 가장자리를 가진 노드가 더 적지만 더 "중요한" 가장자리를 가진 노드보다 더 영향력이 있습니까? 소셜 네트워크에서 중요한 에지를 구성하는 것은 무엇입니까?
이러한 모호성을 해결하기 위해 네트워크 과학자들은 중심성을 측정하는 여러 가지 방법을 개발했습니다. 여기에서는 더 많은 것을 사용할 수 있지만 일반적으로 사용되는 네 가지 방법에 대해 설명합니다.
도
가장 일반적이고 직관적인 측정은 정도 중심성입니다. 차수 중심성의 이면에 있는 아이디어는 간단합니다. 노드의 차수로 영향을 측정합니다. 그래프가 지시된 경우 변형이 있을 수 있습니다. 이 경우 내도와 외도를 측정할 수 있습니다. 첫 번째는 허브 점수, 두 번째는 권위 점수입니다.
이 시리즈의 첫 번째 기사에서는 무방향 접근 방식을 사용했습니다. 이번에는 indegree 접근에 초점을 맞춥니다. 이렇게 하면 단순히 자주 리트윗하는 사용자보다 다른 사람이 리트윗하는 사용자를 강조하여 보다 정확한 분석이 가능합니다.
고유 벡터
고유 벡터 측정은 차수 중심성을 기반으로 합니다. 영향력 있는 노드가 주어진 노드를 더 많이 가리킬수록 점수가 높아집니다. 행과 열이 노드를 나타내는 인접 행렬 로 시작하고 1 또는 0을 사용하여 주어진 행과 열의 해당 노드가 연결되어 있는지 여부를 나타냅니다. 주요 계산은 행렬의 고유 벡터를 추정합니다. 주요 고유 벡터는 우리가 원하는 중심성 측정을 포함할 것이며, 여기서 i 위치는 노드 i 의 중심성 점수를 보유할 것입니다.
페이지 랭크
PageRank는 Google의 핵심에 있는 고유 벡터 측정의 변형입니다. Google이 사용하는 정확한 방법은 알려져 있지 않지만 일반적인 아이디어는 각 노드가 1의 점수로 시작한 다음 점수를 각 가장자리에 동일한 부분으로 분배한다는 것입니다. 예를 들어 노드에 세 개의 가장자리가 있는 경우 각 가장자리를 통해 점수의 1/3을 "전송"합니다. 동시에 노드는 노드를 가리키는 가장자리에 의해 더 중요해집니다. 그 결과 N 개의 미지수가 있는 N 방정식의 풀 수 있는 시스템이 생성됩니다.
사이
네 번째 측정값인 사이 는 매우 다른 접근 방식을 사용합니다. 여기서 노드는 다른 노드들 사이의 짧은 경로에 많이 포함되어 있으면 영향력이 있다고 한다. 즉, "다른 세계"를 연결하여 다른 많은 노드와 통신하는 역할을 합니다.
예를 들어, 소셜 네트워크 분석에서 이러한 종류의 노드는 다른 사람들이 새로운 직업을 찾거나 새로운 연결을 만드는 데 도움이 되는 유형의 사람들로 이해될 수 있습니다. 그들은 이전에 알려지지 않은 소셜 서클의 문입니다.
어떤 것을 사용해야 합니까?
적절한 중심성 측정은 분석 목표에 따라 다릅니다. 양적으로 다른 사람들이 자주 뽑는 사용자를 알고 싶습니까? 학위 중심성이 최선의 선택일 것입니다. 아니면 품질을 고려한 중심성 측정을 선호합니까? 이 경우 고유 벡터 또는 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명의 사용자를 확인할 수 있습니다.
도 | |
고유 벡터 | |
페이지 랭크 | |
사이 | |
결과:
도 | 고유 벡터 | 페이지 랭크 | 사이 | ||||
---|---|---|---|---|---|---|---|
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 |
축구 | 4054 | 액츄풋_ | 0.34 | 트롤축구 | 0.013 | 예미하잔 | 62534 |
PSG_에스파놀 | 3616 | 마트발 | 0.34 | ESPNFC | 0.01 | 해시태그2weet | 61123 |
이바이아웃 | 3258 | ESPNFC | 0.3 | PSG_에스파놀 | 0.007 | 안젤라_FCB | 60991 |
액츄풋_ | 3175 | 축구 | 0.25 | 인스턴트풋 | 0.007 | 자이욘_ | 57269 |
푸티유머 | 2976 | 세일러문아미 | 0.22 | 이바이아웃 | 0.006 | 크루매트19 | 53758 |
문도다볼라 | 2778 | 존스빌팻 | 0.2 | 2010미스터칩 | 0.006 | MdeenOlawale | 49572 |
처음 세 가지 측정값이 PSG_inside, ESPNFC, CrewsMat19 및 TrollFootball과 같은 여러 사용자를 공유한다는 것을 알 수 있습니다. 그들이 토론에 강한 영향을 미쳤다고 가정할 수 있습니다. 사이는 중심성을 측정하는 다른 접근 방식을 사용하므로 다른 기술과 많이 겹치지 않습니다.
참고: 이 기사에 언급된 Twitter 계정의 견해는 Toptal 또는 작성자의 견해를 반영하지 않습니다.
다음 이미지에서 두 개의 사용자 레이블 오버레이가 있는 원래의 컬러 네트워크 그래프를 볼 수 있습니다. 첫 번째 노드에서 노드는 PageRank 점수로 강조 표시되고 두 번째 노드에서는 중간 점수로 강조 표시됩니다.
Gephi를 사용하여 이러한 이미지를 재현할 수 있습니다. 통계 패널의 네트워크 직경 버튼을 사용하여 중간 또는 PageRank 점수를 추정할 수 있습니다. 그런 다음 이 시리즈의 첫 번째 기사에서 설명한 대로 속성을 사용하여 노드 이름을 표시할 수 있습니다.
텍스트 분석: R 및 LDA
또한 소셜 네트워크 토론을 분석하여 사용자가 이야기한 내용을 식별할 수 있습니다. 이에 접근하는 방법은 여러 가지가 있습니다. 함께 나타나는 경향이 있는 단어 집합을 추정할 수 있는 비지도 머신 러닝 기술인 LDA(Latent Dirichlet Allocation)를 통해 주제 모델링을 수행합니다. 그런 다음 해당 단어 집합을 통해 논의 중인 주제를 유추할 수 있습니다.
첫 번째 단계는 텍스트를 소독하는 것입니다. 이를 위해 다음 함수를 정의합니다.
# 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
다음 표에는 감지된 가장 중요한 5가지 주제와 이를 예시하는 10가지 주요 단어가 자세히 설명되어 있습니다.
t_1 | t_2 | t_3 | t_4 | t_5 | |
---|---|---|---|---|---|
1 | 메시 | 메시 | 메시 | 메시 | 메시 |
2 | 라이오넬 | 인스 타 그램 | 리그 | 동부 표준시 | PSG |
삼 | 리오넬 메시 | 게시하다 | 이기다 | 일 | 사자 별자리 |
4 | PSG | 백만 | 목표 | 오 | 레오_메시 |
5 | 마드리드 | 좋아하는 | 채널 | 붓다 | 아오라 |
6 | 진짜 | 스포 | 이온 | 우선권 | 컴파 |
7 | 바르셀로나 | 염소 | 채널 | 에벡 | 바 |
8 | 파리 | PSG | UCL | 뒤 | 세르 |
9 | 레알_마드리드 | 술집 | 풍선 | 퀴 | 쥬가도르 |
10 | mbapp | 더 큰 | 세계 | 제 | 메이저 |
이 커뮤니티의 대부분의 사용자는 영어 사용자이지만 여전히 많은 프랑스어 및 스페인어 사용자가 있습니다(표의 t_4 및 t_5). 첫 번째 주제는 메시의 전 팀(FC 바르셀로나), 두 번째 주제는 인스타그램에 올린 메시의 글, 세 번째 주제는 메시의 업적에 초점을 맞춘 것으로 추측할 수 있다.
이제 주제가 있으므로 가장 많이 논의된 주제를 예측할 수 있습니다. 이를 위해 먼저 사용자의 트윗을 연결합니다(다시 말하지만 가장 큰 커뮤니티에서).
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을 사용한 소셜 네트워크 분석: 사용자 지정 시각적 개체 가이드