R 텍스트마이닝 & 의미연결망(SNA) 완벽 실전 가이드: tidytext·TF-IDF·감성분석·LDA 토픽모델링·tidygraph

R tidytext 생태계를 활용한 현대적 텍스트마이닝 엔드투엔드 가이드입니다. 토큰화 전처리, 불용어 처리, 어간 추출, TF-IDF 핵심어 추출, 감성 사전 분석부터 LDA 토픽모델링 및 tidygraph/ggraph 기반 텍스트 의미연결망 5대 중심성 분석까지 완벽 정리합니다.
데이터·통계
저자

Ikmyungterran

공개

2026년 8월 29일

Modified

2026년 8월 29일

0. 핵심 요약: 현대적 R 텍스트마이닝 파이프라인 개요

빅데이터 분석과 자연어 처리(NLP) 분야에서 비정형 텍스트로부터 유의미한 비즈니스 인사이트를 도출하는 텍스트마이닝(Text Mining)은 필수적인 역량입니다.

과거 R에서는 tm 패키지 기반의 복잡한 말뭉치(Corpus) 및 희소 행렬(Sparse Matrix) 객체를 주로 다루었으나, 오늘날에는 Julia Silge와 David Robinson이 정립한 tidytext 프레임워크를 통해 tidyverse 문법(dplyr, ggplot2, tidyr)과 100% 결합된 현대적 텍스트 분석이 표준으로 자리 잡았습니다.

현대적 R 텍스트마이닝 4단계 파이프라인 구조도

0-1. 전통적 tm 방식 vs 현대적 tidytext 패러다임 비교

비교 항목 전통적 방식 (tm 패키지) 현대적 방식 (tidytext 패러다임)
데이터 구조 전용 Corpus 객체 및 TermDocumentMatrix (희소 행렬) 표준 tibble (1행 = 1토큰 원칙)
데이터 전처리 tm_map() 함수를 통한 단계별 인플레이스 변환 dplyr (filter(), mutate(), anti_join()) 표준 동사 활용
시각화 호환성 별도 변환 함수 필요, 베이스 R 플롯 의존 ggplot2ggraph와 완벽한 네이티브 연동
토픽 & 네트워크 확장 수동 행렬 변환 및 데이터 가공 필요 cast_dtm(), widyr, tidygraph와 유기적 파이프라인 구성
[ Tidy Text 분석 철학 ]
원시 텍스트 테이블 ──(unnest_tokens)──> 1행 1토큰 Long-format ──(anti_join)──> 정제 토큰
         │
         ├──> [단어 빈도 & TF-IDF] ──> 핵심 키워드 도출
         ├──> [감성 사전 매핑] ──> 문서별 정서 점수화
         ├──> [cast_dtm] ──> LDA 토픽 모델링 (주제 군집화)
         └──> [widyr pairwise] ──> tidygraph/ggraph 의미연결망(SNA)

1. 텍스트 정제 및 토큰화(Tokenization) 기초

1-1. tidytext::unnest_tokens()의 강력함

텍스트 분석의 첫걸음은 긴 문장을 분석 단위인 토큰(Token, 단어·형태소·n-gram 등)으로 쪼개는 토큰화(Tokenization)입니다.

library(tidyverse)
library(tidytext)
library(SnowballC)

# 1. 예제 텍스트 데이터프레임 생성
text_df <- tibble(
  doc_id = c("doc1", "doc1", "doc2", "doc2", "doc3"),
  text = c(
    "Artificial intelligence and machine learning drive modern data science.",
    "Deep learning neural networks require robust training models.",
    "Clinical trials and medical patients demand high healthcare accuracy.",
    "Healthcare diagnostics benefit greatly from machine learning prediction.",
    "Financial investment portfolios optimize stock market returns and risk."
  )
)

# 2. 토큰화 (Unnesting Tokens)
# - input: 원본 텍스트 컬럼
# - output: 생성될 단어 컬럼
# - 기본적으로 소문자 변환(tolower=TRUE)과 문장부호 제거 자동 수행
tidy_tokens <- text_df %>%
  unnest_tokens(output = word, input = text)

print(head(tidy_tokens, 8))

1-2. 불용어(Stopwords) 제거 및 정규표현식 클리닝

‘and’, ‘the’, ’is’와 같이 문법적으로 빈번하지만 의미적 가치가 적은 단어들을 불용어(Stopwords)라고 합니다. tidytextstop_words 내장 사전을 활용하여 anti_join()으로 손쉽게 필터링할 수 있습니다.

# 사용자 정의 추가 불용어 정의
custom_stopwords <- tibble(word = c("drive", "require", "demand", "benefit"))

# 불용어 제거 및 숫자·특수기호 클리닝
cleaned_tokens <- tidy_tokens %>%
  anti_join(stop_words, by = "word") %>%
  anti_join(custom_stopwords, by = "word") %>%
  filter(!str_detect(word, "^[0-9]+$")) %>%  # 순수 숫자 제거
  filter(nchar(word) > 2)                     # 2글자 이하 단어 제외

1-3. 어간 추출(Stemming, SnowballC::wordStem)

‘networks’, ‘networked’, ‘networking’ 등 동일 어근에서 파생된 단어들을 단일 기본형(network)으로 통일하는 과정입니다:

# SnowballC 기반 포터 스테머(Porter Stemmer) 적용
stemmed_tokens <- cleaned_tokens %>%
  mutate(stem = wordStem(word, language = "porter"))

2. 단어 빈도와 TF-IDF(단어 빈도-역문서 빈도) 가중치 분석

TF-IDF 수식 구조 및 문서별 상위 핵심 단어 가중치 시각화

2-1. 단순 빈도(TF)의 한계와 TF-IDF 수식 원리

단순히 문서 내 등장 횟수(Term Frequency)만으로 중요도를 평가하면, 모든 문서에 흔하게 등장하는 일반적인 단어가 상위를 차지하게 됩니다. 이를 보정하기 위해 TF-IDF를 사용합니다:

\[\mathrm{TF}(t, d) = rac{f_{t,d}}{\sum_{t' \in d} f_{t',d}} \quad ( ext{문서 } d ext{ 내 단어 } t ext{의 출현 비율})\]

\[\mathrm{IDF}(t, D) = \ln\left( rac{|D|}{|\{d \in D : t \in d\}|} ight) \quad ( ext{전체 문서군 } D ext{ 중 단어 } t ext{가 등장한 문서 수의 역수 로그})\]

\[\mathrm{TF ext{-}IDF}(t, d, D) = \mathrm{TF}(t, d) imes \mathrm{IDF}(t, D)\]

  • 높은 TF-IDF: 특정 문서에서는 자주 등장하지만, 다른 일반 문서들에서는 거의 등장하지 않는 문서의 고유한 주제 핵심어.
  • 낮은 TF-IDF: 모든 문서에 골고루 등장하여 정보 구별력이 없는 단어 (\(\mathrm{IDF} pprox 0\)).

2-2. tidytext::bind_tf_idf() 실전 활용

# 문서별 단어 빈도 집계 후 TF-IDF 산출
doc_tf_idf <- cleaned_tokens %>%
  count(doc_id, word, sort = TRUE) %>%
  bind_tf_idf(term = word, document = doc_id, n = n)

# 문서별 상위 TF-IDF 키워드 확인
top_tf_idf <- doc_tf_idf %>%
  group_by(doc_id) %>%
  slice_max(tf_idf, n = 3, with_ties = FALSE) %>%
  ungroup()

print(top_tf_idf)

2-3. TDM과 DTM 변환 (cast_tdm, cast_dtm)

머신러닝이나 토픽 모델링 패키지와의 호환을 위해 Tidy 테이블을 희소 행렬(Sparse Matrix)로 변환할 수 있습니다:

# 1. Document-Term Matrix (DTM) 생성 (행: 문서, 열: 단어)
dtm_matrix <- doc_tf_idf %>%
  cast_dtm(document = doc_id, term = word, value = n)

# 2. Term-Document Matrix (TDM) 생성 (행: 단어, 열: 문서)
tdm_matrix <- doc_tf_idf %>%
  cast_tdm(term = word, document = doc_id, value = tf_idf)

3. 어휘 사전 기반 감성 분석(Sentiment Analysis)

NRC 8대 기본 정서 감성 사전 분포 프로파일 차트

3-1. 감성 분석 3대 대표 사전

tidytext::get_sentiments() 함수를 통해 표준 감성 사전을 불러와 inner_join()으로 분석합니다:

  1. bing (Bing Liu Lexicon): 단어를 긍정(positive)과 부정(negative)의 2진 범주로 분류.
  2. afinn (Finn Årup Nielsen): 단어별로 -5(극단적 부정)부터 +5(극단적 긍정)까지 정수 점수 부여.
  3. nrc (Saif Mohammad & Peter Turney): 긍·부정 외에 Plutchik의 8대 기본 감정(Anger, Anticipation, Disgust, Fear, Joy, Sadness, Surprise, Trust)으로 다차원 매핑.
# 1. Bing 사전을 활용한 긍정/부정 스코어링
bing_sentiment <- cleaned_tokens %>%
  inner_join(get_sentiments("bing"), by = "word") %>%
  count(doc_id, sentiment) %>%
  pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
  mutate(net_sentiment = positive - negative)

# 2. NRC 사전을 활용한 다차원 정서 분포 도출
nrc_emotions <- cleaned_tokens %>%
  inner_join(get_sentiments("nrc"), by = "word") %>%
  count(sentiment, sort = TRUE)

4. 잠재 디리클레 할당(LDA) 토픽 모델링

토픽 모델링은 대규모 문서 집합에서 명시적으로 드러나지 않은 잠재적 주제(Latent Topics)를 통계적 확률 모델로 찾아내는 비지도 학습 기법입니다.

LDA 토픽 모델링 4대 토픽별 상위 단어 베타 확률 분포 플롯

4-1. 최적 토픽 수(\(k\)) 결정: Perplexity(혼란도)

토픽의 개수 \(k\)를 사전에 지정해야 하므로, 다양한 \(k\) 값에 대해 Perplexity(혼란도: 모형이 새로운 데이터를 얼마나 잘 예측하는가의 척도, 낮을수록 우수)를 계산하여 엘보우 포인트를 찾습니다.

library(topicmodels)

# LDA 모형 적합 (k = 4 예시)
lda_model <- LDA(dtm_matrix, k = 4, method = "Gibbs", control = list(seed = 1234))

# 1. Beta 매트릭스 (토픽-단어 확률: 각 토픽에서 특정 단어가 생성될 확률)
topic_word_probs <- tidy(lda_model, matrix = "beta")

# 각 토픽별 상위 5개 핵심 단어 추출
top_terms <- topic_word_probs %>%
  group_by(topic) %>%
  slice_max(beta, n = 5) %>%
  ungroup() %>%
  arrange(topic, -beta)

# 2. Gamma 매트릭스 (문서-토픽 확률: 각 문서가 특정 토픽에 속할 비중)
doc_topic_probs <- tidy(lda_model, matrix = "gamma")

5. 텍스트 의미연결망 분석(Semantic Network Analysis, SNA)

텍스트 내 단어들은 고립되어 쓰이지 않고 서로 긴밀한 맥락 속에서 공존합니다. 의미연결망 분석(SNA)은 단어 간의 동시 출현(Co-occurrence) 관계를 네트워크 그래프로 모델링하여 핵심 매개 단어와 군집 구조를 파악합니다.

텍스트 의미연결망 분석 동시출현 네트워크 및 중심성 시각화

5-1. 동시출현 빈도 및 상관계수 (widyr)

library(widyr)

# 1. 문서 내 단어 동시출현 빈도 (Co-occurrence Count)
word_pairs <- cleaned_tokens %>%
  pairwise_count(item = word, feature = doc_id, sort = TRUE)

# 2. 단어 간 상관계수 (Phi Coefficient / Pearson Correlation)
word_cors <- cleaned_tokens %>%
  group_by(word) %>%
  filter(n() >= 2) %>%
  pairwise_cor(item = word, feature = doc_id, sort = TRUE)

5-2. tidygraph 기반 네트워크 5대 중심성(Centrality) 지표

tidygraph 패키지는 노드(단어)와 엣지(동시출현 관계)를 하나의 일급 객체(tbl_graph)로 다루며 강력한 중심성 함수를 제공합니다:

library(tidygraph)
library(ggraph)

# 동시출현 네트워크 객체 생성 및 5대 중심성 산출
word_graph <- word_pairs %>%
  filter(n >= 2) %>%
  as_tbl_graph(directed = FALSE) %>%
  activate(nodes) %>%
  mutate(
    degree_cent      = centrality_degree(weights = n),      # 1. 연결 중심성 (지역적 연결도)
    betweenness_cent = centrality_betweenness(weights = n), # 2. 매개 중심성 (브리지 역할)
    closeness_cent   = centrality_closeness(weights = n),   # 3. 근접 중심성 (정보 전파 속도)
    eigen_cent       = centrality_eigen(weights = n),       # 4. 고유벡터 중심성 (영향력 있는 이웃)
    pagerank_cent    = centrality_pagerank(weights = n),    # 5. 페이지랭크 (확률적 중요도)
    community        = as.factor(group_infomap())           # 커뮤니티 탐지
  )

5-3. 허브(Hub) 유형 판별과 Participation Coefficient (참여계수)

네트워크 내 핵심 노드는 다음과 같이 모듈 내/외 연결 패턴에 따라 체계적으로 분류됩니다:

허브 분류 Participation Coefficient (PC) Within-Module Degree Z-Score 네트워크 상의 핵심 역할
Provincial Hub (지역 허브) 낮음 (\(< 0.3\)) 높음 (\(Z > 2.0\)) 특정 주제 군집 내부에서 압도적으로 연결된 핵심 키워드
Connector Hub (연결자 허브) 높음 (\(> 0.7\)) 높음 (\(Z > 2.0\)) 자기 군집뿐 아니라 타 군집과도 왕성히 연결된 핵심 다리(Bridge)
Connector Peripheral 높음 (\(> 0.7\)) 낮음 (\(Z \le 2.0\)) 군집 내 연결은 적으나 서로 다른 주제를 매개하는 가교 단어
Non-Hub Peripheral 낮음 (\(< 0.3\)) 낮음 (\(Z \le 2.0\)) 네트워크 주변부에 위치한 지엽적 단어

5-4. ggraph 출판용 텍스트 네트워크 시각화

# 출판용 ggraph 시각화 코드
ggraph(word_graph, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n, edge_width = n), 
                 edge_color = "#94a3b8", show.legend = FALSE) +
  scale_edge_width(range = c(0.4, 2.2)) +
  geom_node_point(aes(size = degree_cent, color = betweenness_cent)) +
  scale_color_viridis_c(option = "plasma", name = "Betweenness") +
  geom_node_text(aes(label = name), repel = TRUE, fontface = "bold", size = 4.5) +
  theme_void() +
  labs(title = "Semantic Word Co-occurrence Network with GGraph")

6. 실전 End-to-End R 종합 코드 템플릿

아래 코드는 외부 의존 파일 없이 즉시 실행 가능한 완전한 텍스트마이닝 및 SNA 파이프라인입니다:

# ==============================================================
# R tidytext & Semantic Network Analysis End-to-End Script
# ==============================================================

suppressPackageStartupMessages({
  library(tidyverse)
  library(tidytext)
  library(SnowballC)
  library(topicmodels)
  library(widyr)
  library(tidygraph)
  library(ggraph)
})

# [1] 코퍼스 생성
corpus_raw <- tibble(
  doc_id = paste0("doc_", 1:6),
  text = c(
    "Data science and machine learning algorithms optimize business decisions.",
    "Deep neural network learning models require extensive computational training.",
    "Clinical healthcare trials improve medical diagnostics and patient treatment.",
    "Machine learning diagnostics advance healthcare treatment accuracy significantly.",
    "Financial investment portfolio analysis balances stock market risks and returns.",
    "Portfolio risk optimization relies on predictive data analytics models."
  )
)

# [2] Tidy 전처리 & TF-IDF
tokens_clean <- corpus_raw %>%
  unnest_tokens(word, text) %>%
  anti_join(stop_words, by = "word") %>%
  filter(!str_detect(word, "^[0-9]+$"))

tf_idf_res <- tokens_clean %>%
  count(doc_id, word) %>%
  bind_tf_idf(word, doc_id, n)

cat("=== 상위 5개 TF-IDF 키워드 ===
")
print(head(tf_idf_res %>% arrange(desc(tf_idf)), 5))

# [3] DTM 변환 및 LDA 토픽모델링 (k=2)
dtm_obj <- tf_idf_res %>% cast_dtm(doc_id, word, n)
lda_out <- LDA(dtm_obj, k = 2, control = list(seed = 42))
cat("
=== LDA 토픽별 상위 단어 ===
")
print(terms(lda_out, 4))

# [4] 동시출현 네트워크 & 중심성 산출
word_net <- tokens_clean %>%
  pairwise_count(word, doc_id, sort = TRUE) %>%
  filter(n >= 2) %>%
  as_tbl_graph(directed = FALSE) %>%
  activate(nodes) %>%
  mutate(
    deg = centrality_degree(),
    btw = centrality_betweenness()
  )

cat("
=== 단어 네트워크 중심성 상위 5 노드 ===
")
print(as_tibble(word_net) %>% arrange(desc(btw)) %>% head(5))

7. 실무 텍스트 분석 체크리스트 & Best Practice


📚 참고 문헌 및 공식 출처

구분 자료명 및 공식 도서 주요 내용 바로가기
Tidy Text 바이블 Silge & Robinson (2017) Text Mining with R: A Tidy Approach, O’Reilly Media 공식 웹북 (무료)
LDA 원조 논문 Blei, Ng, & Jordan (2003) Latent Dirichlet Allocation, Journal of Machine Learning Research (JMLR) JMLR 공식 논문
R 네트워크 분석 Pedersen (2020) tidygraph & ggraph: Grammar of Graph and Networks in R tidygraph 공식 포털
CRAN 패키지 CRAN topicmodels & widyr 텍스트 행렬 변환 및 고속 동시출현 연산 패키지 CRAN topicmodels

함께 읽으면 좋은 글