Python 코드를 R 코드로 변경한 후 교수님과 면담을 했다.
고쳐야할 점은 두가지였다.
1. for문을 되도록 사용하지 말 것
2. 여러번 재사용을 하는 경우를 제외하고는 함수를 만들지말고 임의의 함수를 사용할 것
(수정 전)
수집한 news data 중 날짜가 없거나 수집 날짜 기준에 맞지않은 경우 NA로 데이터를 변경하였고, 리스트 중 NA인 경우는 제거하는 코드를 생성하였다.
k=1
for(i in 1:length(news_data)){
if(length(is.na(news_data[[k]]))==1 ){
if(is.na(news_data[[k]])){
news_data[[k]]=NULL
}else{k=k+1}
}else{k=k+1}
}
(수정 후)
news_data = lapply(news_data, function(x){discard(x, is.na)})
news_data = discard(news_data, is.logical)
discard 함수는 특정 기준에 해당하는 list의 값을 삭제시켜준다.
https://rbasall.tistory.com/195 이 블로그에 discard 함수의 예시가 잘 나와있다.
discard 와 is.na를 사용하면 리스트의 값 중 NA를 삭제시킬 수 있다.
discard와 is.na를 이용해 바로 삭제시키려했지만 아쉽게도 news_data의 각 원소는 3개의 값들로 이루어져있어 is.na가 먹히지 않았다. is.na를 사용하면 값이 3개가 뜨는데 discard는 TRUE/FALSE의 내용만 판단하므로 TRUE TRUE TRUE와 같은 값은 판단하지 못한다.
그래서 각 리스트의 원소들을 돌며 NA를 판별하였고, 삭제된 NA 는 값이 없는 logical(0)이 되었다.
그리고 discard와 is.logical을 사용해 각 원소의 type이 logical이면 삭제할 수 있도록 했다.(정상적인 값은 list로 구성되어있다.)
(수정 전)
소문자 변환, 표제어 추출, 불용어 제거는 함수를 생성하여 코드를 구성하였다.
text_token = lapply(text_token, upper_to_lower_line)
upper_to_lower_word = function(word){
word = gsub(pattern = "^[A-Z]{1}[a-z]*$", replacement = tolower(word), x=word)
return(word)
}
(수정 후)
text_token = lapply(text_token, function(line){
sapply(line[[1]], function(word){gsub(pattern = "^[A-Z]{1}[a-z]*$", replacement = tolower(word), x=word)})
})
함수를 생성할 경우 가독성이 떨어지고 코드를 보는 제 3자가 이해하기 힘들다는 단점이 있다. 실제로 코드가 CPU를 이용한 병렬처리로 구성되어있어 upper_to_lower_word 함수를 보려면 스크롤를 좀 내려야한다. 여러번 사용하는 경우가 아니면 여러 변수와 함수를 생성하는 것은 가독성을 떨어뜨린다. 그래서 임의의 함수를 사용해 같이 표현하였다.
코드는 리스트의 각 원소에 있는 word를 하나씩 검사하는 코드이다. Apple -> apple 로 변경해주지만 US -> US로 그대로 둔다. 즉, 문장의 첫번째에 와서 대문자로 표시되는 단어는 소문자로 변경하고 국가나 이름 등 문장의 첫번째가 아닌 문자 그대로가 대문자로 표현된 경우는 대문자를 그대로 두었다. 국가를 뜻하는 US를 소문자로 변경하면 us와 같아지기 때문이다.
(수정 전)
몇개의 전처리를 병렬로 처리한 후 news_data_preprocessing에 담았다. 각 core가 처리하여 리스트로 붙여졌다. 구조는 news_data_preprocessing 안에 원소가 124개가 있다. 2012년 1월부터 2022년 4월까지 총 124개의 달을 나타낸다. 124개의 리스트 안에는 2개의 원소가 있다. 이는 카테고리, 날짜, 본문으로 이루어진 news_data와 본문이 토큰화된 text_token이다. 이 2개의 원소 안에는 각 달마나 수집한 뉴스들이 들어있다. i는 몇 번째 달인지 j는 몇 번째 뉴스인지를 나타낸다.
news_data = list()
text_token = list()
for(i in 1:length(news_data_preprocessing)){
print(paste(i,":",length(news_data_preprocessing[[i]][[1]])))
for(j in 1:length(news_data_preprocessing[[i]][[1]])){
news_data = append(news_data, news_data_preprocessing[[i]][[1]][j]) # news_data
text_token = append(text_token, news_data_preprocessing[[i]][[2]][j]) # tex_token
}
}
(수정 후)
news_data = unlist(lapply(news_data_preprocessing, function(list){list[[1]]}), recursive=FALSE)
text_token = unlist(lapply(news_data_preprocessing, function(list){list[[2]]}), recursive=FALSE)
좀 더 깔끔해졌다. 그리고 정말 빨라졌다. 수정 전엔 1시간이 걸렸지만 수정 후엔 0.05초가 걸린다.
첫 번째 원소만 가지고 와서 news_data를, 두 번째 원소만 가지고 와서 text_token을 만들었다. unlist는 list를 flatten 해주기 위한 것이다. recursive=FALSE로 하면 재귀적으로 해준다. 각 달의 news들을 펼쳐서 리스트로 만들어준다. TRUE로 하는 경우 완전히 전부 펼쳐버린다.
TRUE와 FALSE의 차이는 아래 코드를 실행해보면 알 수 있다.
l.ex <- list(a = list(1:5, LETTERS[1:5]), b = "Z", c = NA)
unlist(l.ex, recursive = FALSE)
unlist(l.ex, recursive = TRUE)
위 코드는 https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/unlist 이 곳에서 찾아볼 수 있다. unlist에 대한 전반적인 내용을 알 수 있다.
(수정 전)
먼저 리스트 형식의 틀을 만든 후 paste 함수를 이용해 모든 단어를 한 문장으로 만들고 집어넣는 코드이다.
sentence = vector(mode="list",length=length(text_token))
for(i in 1:length(text_token)){
sentence[[i]] = paste(text_token[[i]],collapse =" ")
}
(수정 후)
sentence = lapply(text_token, function(x){paste(x, collapse=" ")})
시간은 약 52초에서 24초로 줄었다.
그리고 lapply를 사용하면 굳이 수정 전 처럼 틀을 만들 필요가 없다. 코드도 굉장히 간략해졌다.
(수정 전)
list의 원소가 문장으로 구성된 sentence에 time을 넣고 time의 순서대로 정렬하는 코드이다.
sentence_time=list()
for(i in 1:length(news_data)){
sentence_time[[i]] = c(news_data[[i]][2],sentence[[i]])
}
sentence_time = sentence_time[order(sapply(sentence_time, function(x) x[1], simplify=TRUE))]
(수정 후)
time = unlist(lapply(news_data, function(x)(x[2])))
names(sentence) = time
sentence_time = sentence_time[order(names(sentence))]
news_data에서 시간만 빼서 sentence의 이름에 넣었다. names 함수는 리스트 원소의 이름이다. sentence_time$name 과 같이 이름으로 원소를 불러올 수 있다. 그리고 order 함수를 사용하면 순서대로 위치의 이름을 뽑아준다. 그대로 적용하면 순서대로 정렬할 수 있다.