데이터 분석 방법

본 문서에서는 뉴욕타임즈에서 ’이민(i.e., Immigration)’이라는 키워드를 검색하였을때 제공되는 1000개의 최근 기사에 대한 텍스트를 활용하여 현재 미국 미디어(i.e., NewYorkTimes)에 드러나는 이민자의 미디어 표상에 대해 분석을 진행한다.

1. 수집된 데이터 가져오기

사전에 Python을 통해 수집 및 정제 가공된 이민관련 1000개의 기사 텍스트를 불러온다.

수집된 데이터는 뉴스 발행 일자, 뉴스 분류, 제목, 리드, 기사 본문 링크, 기사 본문이 포함되어 있으며 텍스트 분석에는 제목, 리드, 기사 본문만 추출되어 사용되었다.

또한 텍스트 정제 과정에서는 i) 형태소 분석을 통한 문맥어 추출(i.e., 명사, 형용사, 동사, 부사), ii) 복합단어 형태 변환 적용하기 (e.g., united states -> united_states), iii) 불용어 제거하기 (e.g., have, is, etc.)과 같은 과정이 진행되었다.

setwd(path)
cleanTxt <-readLines("cleanTxt_240527.txt")
# 문서 정보 확인하기
# 문서 길이
length(cleanTxt)
## [1] 2676
# 문서 시작 부분
head(cleanTxt)
## [1] "new challenge asylum seeker lawyer shortage"             
## [2] "biden seek curb flow migrants nicaragua new restriction" 
## [3] "russian war advance worry white_house bus migrants south"
## [4] "other busing program mexico push migrants south"         
## [5] "senate vote border deal democrat seek political edge"    
## [6] "trump vp contender commit accept result"
# 문서 끝 부분
tail(cleanTxt, 1)
## [1] "when grow_doukhobor pacifist religious group emigrate canada_czarist russia jj verigin sometimes arrive home school naked elderly woman try burn family house attempt succeed lament mrespect verigin recently recount episode blaze destroy precious family artifact include correspondence great great grandfather prominent doukhobor leader russian writer leo tolstoy early admirer doukhobor pacifism christian morality elderly woman mr verigin explain part small radical splinter_group doukhobor periodically stripp naked light building fire protest land ownership view excessive materialism charge arson motive get deport mother russia day ukraine war rage doukhobor no_longer aspire return russia mrespect verigin lead largest doukhobor organization canada study moscow fire year_grab headline canada polarize doukhobor thing past stress pacifism core mean doukhobor war end residual desire remain return russia mrespect verigin executive director union spiritual community christ feel emotion ukrainian brothers sister face repression russia 18th century doukhobor  name russian phrase meaning spirit wrestler  reject icon worship russian orthodox church resist serve imperial military thousand doukhobor soldier set fire weapon lead group violent suppression exile tolstoy devote royalty novel resurrection help finance doukhobor transit canada emigrate become saskatchewan help canadian prairy majority resettle rural mountainous region southern british_columbia sleepy farming town castlegar_grand fork estimate people doukhobor descent reside canada decade live ascetic communal life reminiscent quaker mennonite suffuse russian culture tradition historically vegetarian shunn alcohol motto toil peaceful life doukhobor canada still speak russian send child russian language school s russian hymn weekly spiritual meeting bato russian style steam bath eat traditional dish borscht doukhobor way life buffet intermarriage allure city life younger_generation draw tiktok tolstoy today doukhobor doctor professor lawyer professional athlete at_least case drag queen assimilation challenge way life mr verigin recent choir practice doukhobor cultural center popoff nurse purple hair lead choir rousing version leonard cohen hallelujah russian follow spirited rendition english queen somebody love doukhobor important culture evolve keep go m popoff discussion turn war rehearsal break choir member ag reject authoritarianism militarism russia president v putin feel connection mother russia russia mother singer kelly poznikoff mr verigin anger ukraine conflict several doukhobor recent month deny service local shop castlegar past prejudice doukhobor canada fan extremist splinter_group son freedom 1920 begin marching nude protest torch public building home member_group oppose property ownership public_schooling child 1950 dozen child forcibly send government boarding school radical mary braun age sentenc year prison set fire community college build british_columbia sentenc m braun disro court previously go numerous fast light small fire courtroom nadja kolesnikoff yoga instructor_grow son freedom household confuse age paternal grandmother burn house jaile year suppose pull together community never ask m kolesnikoff upbringing empower family use kerosene lamps store vegetable fruit underground winter luxury frown learn self sufficient day feel nothing phone costa rica now live doukhobor discovery center castlegar museum director ryan dutchak doukhobor past decade change russian sound last_name fear ostracize canada_census people identify doukhobor stigmatize push people away elders preserve russian language hold key group survival recent thursday dozen doukhobor_gather spiritual meeting wear colorful kerchief blouse skirt apron woman sit side man table lay loaf bread salt pitcher water traditional symbol doukhobor hospitality gospodi blagoslovi lord grant bless singe lord prayer melodious russian stand front classroom elementary_school castlegar verigin russian teacher acknowledge challenge preserve doukhobor faith younger_generation want quick fix spirituality lifelong process hard compete year old daughter instagram facebook competing pull canadian russian doukhobor identity complicate aj robert video_game designer vancouver_grow castlegar regret russian rusty learn borscht mother bring jar visit proud canadian shy away doukhobor war ashamed russian background \""

2. 문서 내 단어 빈도를 확인하기 위한 정제 과정

정제된 기사 텍스트내의 단어 빈도 분석을 진행하기 위해 아래와 같은 정제 과정을 진행한다.

#tibble형태 사용하기
cleanTxt_tb <- as_tibble(cleanTxt)
#id 값 추가하기 - 동출 출현 단어 빈도 계산시 사용
cleanTxt_tb_id <- cleanTxt_tb %>%
  select(value) %>%
  mutate(id = row_number())
# 문장 기준 토큰화
cleanTxt_tb_id_words <- cleanTxt_tb_id %>%
  unnest_tokens(input = value,        # 토큰화할 텍스트
                output = word,        # 출력 변수명
                token = "words", 
                drop = F)  
# 단어 빈도 기준 정렬
cleanTxt_tb_id_words_sort <- cleanTxt_tb_id_words %>%
  count(word, sort = T)
# 세개의 알파벳 미만 조합으로 이루어진 단어들 제거
cleanTxt_tb_id_words_sort_3 <- cleanTxt_tb_id_words_sort %>%
  filter(str_count(word) > 3)
#최 상위 1000개 빈출 단어 추출
cleanTxt_tb_id_words_sort_3_top1000 <- cleanTxt_tb_id_words_sort_3 %>%
  head(1000)
#최 상위 50개 빈출 단어 추출
cleanTxt_tb_id_words_sort_3_top50 <- cleanTxt_tb_id_words_sort_3 %>%
  head(50)

3. 문서 내 단어 빈도 빈도 시각화

3.1. 단어 빈도 그래프(Zipf’s law graph) 생성

ggplot(cleanTxt_tb_id_words_sort_3_top50, aes(x = reorder(word, n), y = n)) +
  geom_col() +
  coord_flip() +
  geom_text(aes(label = n), hjust = -0.3) +            
  labs(title = "Zipf's law graph of newyork times on immigration",  
       x = "frequency", y = "words") +                           
  theme(title = element_text(size = 12))  

3.2. 워들(Wordcloud) 생성

cleanTxt_tb_id_words_sort_3_top50_df <- as.data.frame(cleanTxt_tb_id_words_sort_3_top50)
pal <- brewer.pal(8,"Dark2") 
wordcloud(words=cleanTxt_tb_id_words_sort_3_top50_df$word, 
          freq=cleanTxt_tb_id_words_sort_3_top50_df$n, 
          min.freq=3,    
          random.order=F, 
          random.color=T, 
          colors=pal) 

단어 빈도 분석 결과, 이민에 대한 뉴욕타임즈 기사에서 가장 많이 등장하는 단어는 immigration, border, biden등이 었으며, immigration, border의 경우 출변 빈도수가 3000회 이상으로 다른 단어들에 비해 1.4배 가량 많은 것으로 확인되었다.

4. 단어 임베딩을 통한 분석

4.1. word2vec을 통한 단어 임베딩 생성

단어임베딩이란 비정형화된 단어를 숫자로 바꿔줌으로써 사람의 언어를 컴퓨터 언어로 번역하는 것을 이야기하며 word2vec은 분산 표현을 통해 벡터를 생성하는 과정에 간단한 인공신경망 모형을 사용하여 속도와 정확도를 향상시킨 모델이다.

본 연구에서는 Word2Vec에서 사용하는 주변에 있는 단어들로 중간에 있는 단어들을 예측하는 CBoW(Continuous Bag of Words)와 중심 단어를 보고 어떤 주변 단어가 존재하는지 예측하는 모델인 Skip-Gram중 Skip-Gram을 사용하였으며, 아래와 같이 모델을 생성하였다.

  • vector_size=100
  • window=5
  • min_count=2
  • workers=4
  • sg=1
setwd(path)
model <- read.csv("word2vecMat.csv", head = FALSE)
model_clean <- model[-1]
model_clean_mat <- as.matrix(model_clean)
row.names(model_clean_mat) <- model[1]$V1
embedding <- model_clean_mat
embedDF <- as.data.frame(embedding)

4.3. t-SNE를 통한 차원 축소

t-SNE는 기존의 차원 축소 방법들보다 정보의 양은 최대한 유지하면서 차원을 축소하기 때문에 정보 손실을 최소화 할 수 있어서 최근 많이 사용되고 있는 차원축소 방법이다. 본 연구는 다차원으로 이루어져있는 단어 임베딩 결과중 가장 많이 사용된 빈출단어 1000개를 추출하였다. 그 후 해당 단어들의 임베딩 결과의 t-SNE를 통해 2차원으로 축소하고 아래와 같이 시각화 하였다.

fwords <- cleanTxt_tb_id_words_sort_3_top1000$word
fembedDF <- NULL
for (i in 1:length(fwords)){
  if (fwords[i] %in% rownames(embedDF)){
    fembedDF <- rbind(fembedDF,embedDF[fwords[i],])
  }
}
tsne <- Rtsne(fembedDF, dims = 2, perplexity=30, verbose=TRUE, max_iter = 1000)
## Performing PCA
## Read the 1000 x 50 data matrix successfully!
## Using no_dims = 2, perplexity = 30.000000, and theta = 0.500000
## Computing input similarities...
## Building tree...
## Done in 0.11 seconds (sparsity = 0.141592)!
## Learning embedding...
## Iteration 50: error is 68.053391 (50 iterations in 0.11 seconds)
## Iteration 100: error is 68.053391 (50 iterations in 0.10 seconds)
## Iteration 150: error is 68.053391 (50 iterations in 0.12 seconds)
## Iteration 200: error is 68.053391 (50 iterations in 0.16 seconds)
## Iteration 250: error is 68.053391 (50 iterations in 0.19 seconds)
## Iteration 300: error is 2.055831 (50 iterations in 0.15 seconds)
## Iteration 350: error is 1.696936 (50 iterations in 0.08 seconds)
## Iteration 400: error is 1.637436 (50 iterations in 0.08 seconds)
## Iteration 450: error is 1.608833 (50 iterations in 0.08 seconds)
## Iteration 500: error is 1.594700 (50 iterations in 0.08 seconds)
## Iteration 550: error is 1.584555 (50 iterations in 0.08 seconds)
## Iteration 600: error is 1.571231 (50 iterations in 0.08 seconds)
## Iteration 650: error is 1.559204 (50 iterations in 0.08 seconds)
## Iteration 700: error is 1.552251 (50 iterations in 0.08 seconds)
## Iteration 750: error is 1.544075 (50 iterations in 0.08 seconds)
## Iteration 800: error is 1.539607 (50 iterations in 0.07 seconds)
## Iteration 850: error is 1.535081 (50 iterations in 0.08 seconds)
## Iteration 900: error is 1.530624 (50 iterations in 0.08 seconds)
## Iteration 950: error is 1.527702 (50 iterations in 0.08 seconds)
## Iteration 1000: error is 1.525526 (50 iterations in 0.08 seconds)
## Fitting performed in 1.93 seconds.
fembedDF_tsne <- data.frame(fembedDF, X = tsne$Y[,1], Y = tsne$Y[,2])
df  <- data.frame(word = rownames(fembedDF),  
                  xpos = gsub(".+//", "", rownames(fembedDF)),  
                  x = tsne$Y[,1], y = tsne$Y[,2],  
                  stringsAsFactors = FALSE) 

plot_ly(df, x = ~x, y = ~y, type = "scatter", mode = 'text', text = ~word) %>% layout(title = "SG Embeddings Visualization")

5. 통계분석

5.1. 단어 군집 분석(비계층적 군집 분석)

군집분석이란 서로 유사한 정도에 따라 다수의 객체를 군집으로 나누는 작업 또는 이에 기반한 분석을 의미한다. 본 연구에서는 확인적 분석 방법인 비계층적 군집 분석과 탐색적 분석 방법인 계층적 군집분석 방법을 모두 사용하였으며, 최적의 군집을 추론하기 위해 NbClust를 기반으로 K-means를 여러번 수행하여 최적의 군집을 아래와 같이 도출하였다.

nc <- NbClust(fembedDF, min.nc = 2, max.nc = 15, method = "kmeans")

## *** : The Hubert index is a graphical method of determining the number of clusters.
##                 In the plot of Hubert index, we seek a significant knee that corresponds to a 
##                 significant increase of the value of the measure i.e the significant peak in Hubert
##                 index second differences plot. 
## 

## *** : The D index is a graphical method of determining the number of clusters. 
##                 In the plot of D index, we seek a significant knee (the significant peak in Dindex
##                 second differences plot) that corresponds to a significant increase of the value of
##                 the measure. 
##  
## ******************************************************************* 
## * Among all indices:                                                
## * 6 proposed 2 as the best number of clusters 
## * 7 proposed 3 as the best number of clusters 
## * 1 proposed 6 as the best number of clusters 
## * 2 proposed 7 as the best number of clusters 
## * 1 proposed 12 as the best number of clusters 
## * 4 proposed 14 as the best number of clusters 
## * 2 proposed 15 as the best number of clusters 
## 
##                    ***** Conclusion *****                            
##  
## * According to the majority rule, the best number of clusters is  3 
##  
##  
## *******************************************************************
par(mfrow=c(1,1))
barplot(table(nc$Best.n[1,]),
        xlab="Numer of Clusters", ylab="Number of Criteria",
        main="Number of Clusters Chosen")

5.2. 단어 군집 분석(계층적 군집 분석)

도출된 최적의 군집 갯수(i.e., 3개)를 사용하여 분석을 수행한 결과 각 군집에 몇개의 단어들이 분포 되어 있는지를 확인할 수 있었다. 군집 분석에 사용한 단어간 유사도 계산 법과 단어 군집 생성 방법은 아래와 같다.

  • 유사도 계산법=cosine
  • 군집 연결법=complete(최장거리연결법)
idist <- dist(fembedDF, method = "cosine")
hc <- hclust(idist,method="complete")
par(mfrow=c(1,1))
plot(hc, hang = -1, cex=0.5) 
rect.hclust(hc, k=clusterNum, border="red")

cutreeHc <- cutree(hc, clusterNum) 
table(cutreeHc) 
## cutreeHc
##   1   2   3 
## 303 481 216

5.3. 군집화된 단어 시각화 (i.e., WordNet)

3개로 군집화된 단어들이 서로 어떻게 공간상에 위치하는지를 확인함으로 어떤 단어들이 서로 유사한지를 확인할 수 있다.

coordinateByCluster <- data.frame(fembedDF, clusterNum = cutreeHc, n = as.data.frame(cleanTxt_tb_id_words_sort_3_top1000)$n, X = tsne$Y[,1], Y = tsne$Y[,2])

#그래프로 생성하기
df  <- data.frame(word = rownames(coordinateByCluster),   
                  x = coordinateByCluster$X, y = coordinateByCluster$Y, color = as.factor(coordinateByCluster$clusterNum),
                  stringsAsFactors = FALSE) 

plot_ly(df, x = ~x, y = ~y, type = "scatter", mode = 'text', text = ~word, color = ~color) %>% layout(title = "WordNet of clustered words")