Proposal Data Career Day 16

Background

Perkembangan teknologi informasi yang pesat membuat sebuah informasi tentang suatu peristiwa tersaji dengan sangat cepat. Dahulu ketika kita ingin mendapatkan sebuah informasi atau berita kita bisa mendapatkannya melalui koran, radio, maupun televisi. Saat ini kita berada di era digital, kita dapat mendapatkan berita tersebut dengan mudah hanya dengan mengetikan kata kunci yang diinginkan pada mesin pencarian. Untuk satu topik atau peristiwa akan muncul berbagai berita dengan konten yang sama namun dengan sentimen yang berbeda. Berita-berita tersebut tersebar di berbagai website berita dan sebagian berita tersebut berbentuk sebuah teks. Dari berbagai data berita teks tersebut sekitar 80% sampai 85% adalah dalam format tidak terstruktur (Bridge, 2011). Tentunya sebuah perusahaan media memerlukan strategi pemasaran untuk produk-produk berita yang ditulis, salah satunya dapat menerapkan sebuah startegi digital marketing.

Digital marketing adalah suatu strategi pemasaran menggunakan media digital dan internet. Konsep dan penerapan digital marketing untuk mendongkrak pemasaran produk dari suatu usaha. Dimana pada perusahaan penyedia berita ingin berita yang sudah disajikan/dihasilkan memiliki jumlah pembaca yang banyak. Salah satu contoh dari media digital adalah situs berita online, media sosial, blog, dll. Tentunya dalam melakukan pemasaran di media digital kita memerlukan sebuah judul maupun headline dari berita yang dihasilkan. Tetapi apakah hal tersebut sudah memberikan sentimen yang tepat terhadap berita yang kita tulis? Untuk mengetahui hal tersebut kita bisa memanfaatkan sebuah analisa, yaitu analisa sentimen.

Analisa sentimen merupakan proses memahami,mengestrak dan mengolah data tekstual secara otomatis untuk mendapatkan informasi sentimen yang terkandung dalam suatu teks. Analisis sentimen dilakukan untuk melihat kecenderungan opini terhadap sebuah objek oleh seseorang, apakah cenderung berpandangan negatif atau positif. Selain dapat melihat kecenderungan dari data teks tersebut, kita bisa mendapatkan atau melakukan ekstrasi keyword yang dapat digunakan dalam SEO. SEO atau Search Engine Optimization adalah sebuah teknik untuk mengoptimasi website agar mendapat ranking teratas di hasil pencarian pada search engine. Dengan SEO, website akan mudah orang temukan sehingga berpotensi meningkatkan traffic pengunjung.

Problem Statement & Project Idea

Dalam ingin meningkatkan jumlah traffic pembaca pada berita yang kita tulis, kita harus memastikan sentimen yang kita tulis sudah sesuai dengan topiknya. Maka dari itu, ide dari project ini adalah membuat model machine learning yang dapat mengklasifikasi sentimen sebuah berita dan memberikan sebuah keluaran hasil interpretasi analisa sentimen sehingga bisa digunakan untuk evaluasi penulisan beserta dengan rekomendasi keyword.

Problem Scope

Pada project ini akan menggunakan data Title and Headline Sentiment Prediction dari situs Kaggle. Dataset ini mengandung informasi sebagai berikut:

Kolom Deskripsi
IDlink Unique id tiap post
Title Judul dari post
Headline Headline dari post
Source Sumber berita
Topic Kategori topik
PublishDate Tanggal di publish nya berita
Facebook Berada di facebook atau tidak
GooglePlus Berada di GooglePlus atau tidak
LinkedIn Berada di LinkedIn atau tidak
SentimentTitle Nilai sentiment pada judul
SentimentHeadline Nilai sentiment pada headline

Informasi yang akan digunakan lebih lanjut pada project ini adalah kolom Title, Headline, SentimentTitle dan SentimentHeadline. Selanjutnya, kebutuhan dalam pembuatan model machine learning untuk melakukan klasifikasi sentimen pada sebuah berita, kolom SentimentTitle dan SentimentHeadline akan dilakukan labeling. Model machine learning yang akan digunakan adalah Naive Bayes, Random Forest, dan LSTM.

Output

Keluaran dari project ini adalah sebuah dashboard, yang akan dibuat dengan R shiny. Pada dashboard ini akan terdapat satu bagian yaitu sentiment analysis, dimana pada bagian tersebut adalah inti dari project ini.

Secara garis besar pada dashboard ini terbagi menjadi 2 bagian, yaitu input dan Hasil. Tiap bagian tersebut memiliki beberapa fitur yang akan dijelaskan sebagai berikut:

Input

Pada bagian ini terdapat 3 buah inputan yang akan digunakan untuk analisa, yaitu 2 buah textInput dan 1 buah radioButtons:

  • Pada textInput akan digunakan untuk memasukan judul dan berita yang ingin kita analisa menggunakan model yang sudah dibuat.
  • Sedangkan pada radioButtons akan diisi dengan tujuan dari berita yang ditulis memiliki sentiment positive atau negative, yang nantinya akan digunakan sebagai kontrol dengan hasil klasifikasi dari model. Sebagai contoh ketika ada sebuah berita terkait bencana alam yang merupakan sebuah berita duka atau memberikan sentimen yang negatif maka kita akan mengisi pada inputan sentiment goal adalah negatif.

Hasil

Pada bagian ini terdapat 3 output, yaitu analysis result, classification result dan keyword recommendation.

  • Pada bagian analysis result akan memberikan informasi dari interpretasi metode LIME pada judul dan headline dari berita, sehingga user dapat melakukan evaluasi kata mana yang tidak sesuai dengan tujuan dari berita tersebut.
  • Tentunya pada bagian ini terdapat hasil klasifikasi sentimen menggunakan model yang sudah dibuat.
  • Sedangkan pada bagian keyword recommendation akan menampilkan hasil ektrasi keyword berdasarkan dari teks inputan yang akan muncul ketika hasil klasifikasi sentimen sudah sesuai dengan sentiment goal. Lanjutan dari contoh pada bagian input, Seperti yang kita ketahui ketika berita yang memberikan informasi terkait suatu bencana alam akan merangkum beberapa informasi seperti kerugian, korban jiwa, atau kerusakan yang terjadi. Dimana informasi-informasi tersebut sudah tepat dengan tujuan dari penyampaian berita dengan topik bencana alam tetapi akan memberikan hasil klasifikasi sentimen yang negatif. Karena sudah sesuai antara hasil klasifikasi dan sentiment goal maka akan menghasilkan sebuah keyword recommendation.

Business Impact

Business impact yang diharapkan oleh penulis sebagai berikut:

  • Project ini dapat mengklasifikasi sentimen pada judul dan headline sebuah berita.
  • Dapat memberikan sebuah masukan/evaluasi yang merupakan hasil dari metode LIME sehingga jika hasil dari klasifikasi sentimen tidak sesuai dengan harapan sentimen pada berita tersebut kita dapat mengetahui kata/bagian mana yang perlu diperbaiki.
  • Dapat menghasilkan rekomendasi keyword menggunakan algoritma RAKE, sehingga dapat digunakan lebih lanjut untuk membuat strategi marketing.
  • Project ini kedepannya dapat dikembangkan untuk industri lain, seperti untuk kegiatan marketing suatu produk pada e-commerce. Dalam pengembangan tersebut tentunya diperlukan data-data yang mendukung dalam pembuatan model machine learning.

Data Wrangling & Exploratory Data Analysis

Library

Berikut adalah library yang digunakan untuk data wrangling dan text pre-processing pada project ini :

# Data Wrangling
library(tidyverse)
library(wordcloud)

# Text Pre-Processing
library(tm)
library(textclean)
library(stringr)
library(caret)

Import Data

Langkah pertama adalah melakukan import dataset Title and Headline Sentiment Prediction, dalam pembuat model machine learning ini kita akan memasukan file train_file.csv menggunakan fungsi read.csv() dah menyimpannya pada variabel news.

news <- read.csv('data/train_file.csv')
head(news)

Untuk melihat struktur data secara general kita dapat menggunakan fungsi glimpse() dari library dplyr.

glimpse(news)
#> Rows: 55,932
#> Columns: 11
#> $ IDLink            <chr> "Tr3CMgRv1N", "Wc81vGp8qZ", "zNGH03CrZH", "3sM1H0W8t…
#> $ Title             <chr> "Obama Lays Wreath at Arlington National Cemetery", …
#> $ Headline          <chr> "Obama Lays Wreath at Arlington National Cemetery. P…
#> $ Source            <chr> "USA TODAY", "Bloomberg", "Bloomberg", "RTT News", "…
#> $ Topic             <chr> "obama", "economy", "economy", "economy", "economy",…
#> $ PublishDate       <chr> "2002-04-02 00:00:00", "2008-09-20 00:00:00", "2012-…
#> $ Facebook          <int> -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -…
#> $ GooglePlus        <int> -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -…
#> $ LinkedIn          <int> -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -…
#> $ SentimentTitle    <dbl> 0.00000000, 0.20833333, -0.42521003, 0.00000000, 0.0…
#> $ SentimentHeadline <dbl> -0.053300179, -0.156385811, 0.139754249, 0.026064302…

Dapat pada hasil diatas bahwa data news memiliki 55,932 observasi/baris dan 11 kolom, untuk deskripsi kolom dapat dilihat pada tabel dibawah:

Kolom Deskripsi
IDlink Unique id tiap post
Title Judul dari post
Headline Headline dari post
Source Sumber berita
Topic Kategori topik
PublishDate Tanggal di publish nya berita
Facebook Berada di facebook atau tidak
GooglePlus Berada di GooglePlus atau tidak
LinkedIn Berada di LinkedIn atau tidak
SentimentTitle Nilai sentiment pada judul
SentimentHeadline Nilai sentiment pada headline

Tidak lupa juga kita harus melakukan pengecekan terhadap missing value pada data news, untuk melakukan pengecekan tersebut kita dapat mengkombinasikan dua fungsi yaitu is.na() dan colSums().

colSums(is.na(news))
#>            IDLink             Title          Headline            Source 
#>                 0                 0                 0                 0 
#>             Topic       PublishDate          Facebook        GooglePlus 
#>                 0                 0                 0                 0 
#>          LinkedIn    SentimentTitle SentimentHeadline 
#>                 0                 0                 0

Pada data news tidak memiliki missing values, selanjutnya kita juga melakukan pengecekan duplicate value pada data kita menggunakan kombinasi fungsi duplicated() dan sum().

sum(duplicated(news)) 
#> [1] 0

Data kita tidak memiliki observasi yang merupakan duplikasi dari observasi lain, dimana kita dapat melanjutkan ke tahap selanjutnya yaitu melakukan labeling sentiment.

Sentiment Labeling

Kedepannya kita hanya akan menggunakan 4 kolom yaitu Title, Headline. SentimentTitle, dan SentimentHeadline untuk melakukan subsetting pada keempat kolom tersebut kita akan menggunakan fungsi select(). Selanjutnya kita akan membuat kolom baru yang berisikan informasi label nilai sentimen menggunakan fungsi mutate() dalam pembagian label tersebut kita akan dibantu dengan fungsi ifelse() jika nilai sentimen lebih besar dari 0 (> 0) maka akan diberikan label ‘positive’, jika kurang dari 0 (< 0) akan diberikan label ‘negative’ sedangkan jika nila sentimennya sama dengan 0 maka labelnya adalah ‘neutral’. Kolom label tersebut akan kita ubah tipe data nya menjadi factor menggunakan fungsi as.factor(). Dikarenakan kita sudah memiliki kolom yang berisikan informasi sentimen berupa label, maka diakhir kita akan mendrop kolom SentimentHeadline dan SentimentTitle menggunakan fugnsi select() agar tidak terdapat informasi yang mirip antar kolom pada data kita. Fungsi-fungsi tersebut akan digunakan menggunakan metode piping dari library dplyr agar memudahkan kita.

news <- news %>% 
  select(Title, Headline, SentimentTitle, SentimentHeadline) %>% 
  mutate(SentimentTitleLable = as.factor(ifelse(SentimentTitle > 0, 'positive', 
                                      ifelse(SentimentTitle == 0, 'neutral', 'negative'))),
         SentimentHeadlineLable = as.factor(ifelse(SentimentHeadline > 0, 'positive', 
                                         ifelse(SentimentHeadline == 0, 'neutral', 'negative')))) %>% 
  select(-c(SentimentHeadline, SentimentTitle)) 

head(news)

Class Propotion

Dalam pembuatan classfication machine learning, kita perlu untuk mengetahui proporsi kelas/label apakah sudah balance atau belum dikarenakan akan mempengaruhi model yaitu condong ke salah satu kelas. Untuk mengetahui proporsi kelas kita akan menggunakan fungsi table() untuk menjumlahkan jumlah data per kelas dan fungsi prop.table() untuk mengubahnya kedalam satuan persen. Proporsi kelas pada kolom SentimentTitleLable sebagai berikut :

news$SentimentTitleLable %>% 
  table() %>% 
  prop.table() 
#> .
#>  negative   neutral  positive 
#> 0.4095866 0.2021383 0.3882750

Sedangkan proporsi kelas pada kolom SentimentHeadlineLable sebagai berikut :

news$SentimentHeadlineLable %>% 
  table() %>% 
  prop.table()
#> .
#>   negative    neutral   positive 
#> 0.57121147 0.03046557 0.39832296

Dapat dilihat dari hasil proporsi kelas data kita belum balance atau seimbang, sehingga kedepannya kita perlu melakukan downsampling atau upsampling untuk membuat seimbang kelas nya.

Text Characteristics

Dalam rangka melakukn eksplorasi data, kita dapat melihat kata mana yang paling sering muncul pada tiap label. Untuk memudahkan kita untuk melakukan hal tersebut kita akan menggunakan fungsi wordcloud() dari library wordcloud yang akan menghasilkan sebuah plot yang nantinya menampilkan 20 kata dengan frekuensi tertinggi.

  • Karakteristik teks pada kolom Title dengan label positive
title_pos <- news %>% 
  filter(SentimentTitleLable == 'positive') %>% 
  select(Title)

wordcloud(words = as.matrix(title_pos),
          max.words = 20,
          scale = c(2.5, 1.25),
          random.order = F,
          rot.per = 0.5,
          colors = brewer.pal(2, 'Set1'))

  • Karakteristik teks pada kolom Title dengan label neutral
title_neu <- news %>% 
  filter(SentimentTitleLable == 'neutral') %>% 
  select(Title)

wordcloud(words = as.matrix(title_neu),
          max.words = 20,
          scale = c(2.5, 1.25),
          random.order = F,
          rot.per = 0.5,
          colors = brewer.pal(2, 'Set1'))

  • Karakteristik teks pada kolom Title dengan label negative
title_neg <- news %>% 
  filter(SentimentTitleLable == 'negative') %>% 
  select(Title)

wordcloud(words = as.matrix(title_neg),
          max.words = 20,
          scale = c(2.5, 1.25),
          random.order = F,
          rot.per = 0.5,
          colors = brewer.pal(2, 'Set1'))

  • Karakteristik teks pada kolom Headline dengan label positive
headline_pos <- news %>% 
  filter(SentimentHeadlineLable == 'positive') %>% 
  select(Headline)

wordcloud(words = as.matrix(headline_pos),
          max.words = 20,
          scale = c(2.5, 1.25),
          random.order = F,
          rot.per = 0.5,
          colors = brewer.pal(2, 'Set1'))

  • Karakteristik teks pada kolom Headline dengan label neutral
headline_neu <- news %>% 
  filter(SentimentHeadlineLable == 'neutral') %>% 
  select(Headline)

wordcloud(words = as.matrix(headline_neu),
          max.words = 20,
          scale = c(2.5, 1.25),
          random.order = F,
          rot.per = 0.5,
          colors = brewer.pal(2, 'Set1'))

  • Karakteristik teks pada kolom Headline dengan label negative
headline_neg <- news %>% 
  filter(SentimentHeadlineLable == 'negative') %>% 
  select(Headline)

wordcloud(words = as.matrix(headline_neg),
          max.words = 20,
          scale = c(2.5, 1.25),
          random.order = F,
          rot.per = 0.5,
          colors = brewer.pal(2, 'Set1'))

Jika dilihat dari hasil plot bahwa kata yang sering muncul pada kolom Title dan Headline adalah economy, microsoft, dan obama.

HTML Entity Check

Selanjutnya kita akan melakukan pengecekan pada data kita apakah terdapat HTML entity, untuk melakukan tersebut kita akan menggunakan fungsi mutate() untuk membuat kolom baru yang menampung informasi apakah data kita memiliki HTML entity atau tidak. Sedangkan untuk nilai pada kolom tersebut kita akan menggunakan fugsi str_extract() dari library stringr.

html_pattern <- '&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});'

check_html <- news %>% select(Title, Headline)

check_html <- check_html %>% 
              mutate(
                    contentHTML_title = str_extract(Title, html_pattern),
                    contentHTML_headline = str_extract(Headline, html_pattern)
                    )

head(check_html)

Kita dapat menggunakan fungsi unique() untuk melihat HTML entity pada data kita.

unique(check_html$contentHTML_title)
#> [1] NA       "&amp;"  "&quot;" "&gt;"
unique(check_html$contentHTML_headline)
#> [1] NA         "&quot;"   "&amp;"    "&middot;" "&gt;"     "&rsquo;"  "&#40;"   
#> [8] "&ldquo;"  "&mdash;"

Jumlah observasi yang memiliki HTML entity sebagai berikut :

sum(!is.na(check_html$contentHTML_title))
#> [1] 619
sum(!is.na(check_html$contentHTML_headline))
#> [1] 5267

Kedepannya kita akan menghilangkan HTML entity ini pada tahap data preparation.

Url Check

Kita juga akan melakukan pengecekan apakah data kita memiliki sebuah url, untuk melakukan hal ini sama seperti pada tahap pengecekan HTML entity.

url_pattern <- 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'

check_url <- news %>% select(Title, Headline)

check_url <- check_url %>% 
              mutate(
                    contentURL_title = str_extract(Title, url_pattern),
                    contentURL_headline = str_extract(Headline, url_pattern)
                    )

head(check_url)

Berikut adalah url yang terdapat data kita.

unique(check_url$contentURL_title)
#> [1] NA
unique(check_url$contentURL_headline)
#>  [1] NA                                                                              
#>  [2] "http://www.prweb.com/releases/2016/03/prweb13238571.htm"                       
#>  [3] "http://www.thestar.com.my"                                                     
#>  [4] "https://t.co/xPcry19sS8."                                                      
#>  [5] "http://www.imemc.org,"                                                         
#>  [6] "http://gawker.com/active-shooter."                                             
#>  [7] "http://www.prweb.com/releases/2015/11/prweb13104594.htm"                       
#>  [8] "https://t.co/njlirCxPkt."                                                      
#>  [9] "http://bit.ly/1jAfZCz"                                                         
#> [10] "http://www.prweb.com/releases/2015/12/prweb13113805.htm"                       
#> [11] "http://www.prweb.com/releases/2015/12/prweb13115886.htm"                       
#> [12] "https://t.co/42vPUfgCgM."                                                      
#> [13] "http://www.prweb.com/releases/Economic/Arts/prweb13123894.htm"                 
#> [14] "http://bit.ly/1mqsbaU"                                                         
#> [15] "http://www.prweb.com/releases/2015/12/prweb13134571.htm"                       
#> [16] "https://t.co/eMSrr1bhlC."                                                      
#> [17] "https://t.co/ZZ6hHRfgIw"                                                       
#> [18] "http://www.youtube.com/embed/A3rKavB0krs"                                      
#> [19] "https://t.co/RUPbcev5jY"                                                       
#> [20] "http://www.prweb.com/releases/2016/01/prweb13148253.htm"                       
#> [21] "http://abc13"                                                                  
#> [22] "http://www.prweb.com/releases/2016/01/prweb13150208.htm"                       
#> [23] "http://indy.st/1RdpQwX"                                                        
#> [24] "http://goo.gl/8Yu3g4V"                                                         
#> [25] "http://www.prweb.com/releases/2016/01/prweb13167807.htm"                       
#> [26] "https://t.co/JJ3kATYXJ7."                                                      
#> [27] "https://t.co/HZcWvsWlZ3."                                                      
#> [28] "http://yusc.ca/mural/palestinian-roots/)"                                      
#> [29] "https://t.co/grG0RwlRNl."                                                      
#> [30] "http://www.prweb.com/releases/2016/02/prweb13196567.htm"                       
#> [31] "http://bit.ly/20tllni"                                                         
#> [32] "https://en.wikipedia.org/wiki/Chairman_of_the_"                                
#> [33] "https://t.co/"                                                                 
#> [34] "http://www.prweb.com/releases/2016/02/prweb13227264.htm"                       
#> [35] "http://www.prweb.com/releases/2016/02/prweb13225850.htm"                       
#> [36] "http://www.bbc.com/news/world-middle-east-33521655"                            
#> [37] "http://www.prweb.com/releases/2016/02/prweb13231017.htm"                       
#> [38] "http://www.prweb.com/releases/2016/02/prweb13237356.htm"                       
#> [39] "https://www.fitchratings.com/creditdesk/reports/report_frame.cfm?rpt_id=878254"

Jumlah observasi yang mengandung url berjumlah :

sum(!is.na(check_url$contentURL_title))
#> [1] 0
sum(!is.na(check_url$contentURL_headline))
#> [1] 53

Data Preparation

Berikut adalah 6 observasi teratas pada data news :

news$Headline %>% head()
#> [1] "Obama Lays Wreath at Arlington National Cemetery. President Barack Obama has laid a wreath at the Tomb of the Unknowns to honor"                                                                     
#> [2] "Tim Haywood, investment director business-unit head for fixed income at Gam, discusses the China beige book and the state of the economy."                                                           
#> [3] "Nouriel Roubini, NYU professor and chairman at Roubini Global Economics, explains why the global economy isn't facing the same conditions"                                                           
#> [4] "Finland's economy expanded marginally in the three months ended December, after contracting in the previous quarter, preliminary figures from Statistics Finland showed Monday. "                    
#> [5] "Tourism and public spending continued to boost the economy in January, in light of contraction in private consumption and exports, according to the Bank of Thailand data. "                         
#> [6] "Over 100 attendees expected to see latest version of Microsoft Dynamics SL and Dynamics GP (PRWeb February 29, 2016) Read the full story at http://www.prweb.com/releases/2016/03/prweb13238571.htm "

Dapat dilihat bahwa data kita perlu dilakukan beberapa tahap preparation sebelum digunakan untuk pembuatan model machine learning.

Text Cleansing

Pada tahap ini kita akan mecoba mempersiapkan data news kita, dikarenkan data ini merupakan data yang tidak terstruktur berupa teks maka kita harus melaukan beberapa step cleansing. Sebagai contoh untuk text cleansing kita akan menggunakan variabel example.

example <- 'WASHINGTON (AP) — President Barack Obama said Thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in California and called on Americans to &quot;search ourselves as a society&quot; to find a way to curb gun violence. Over 100 attendees expected to see latest version of Microsoft Dynamics SL and Dynamics GP (PRWeb February 29, 2016) Read the full story at http://www.prweb.com/releases/2016/03/prweb13238571.htm '

example
#> [1] "WASHINGTON (AP) — President Barack Obama said Thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in California and called on Americans to &quot;search ourselves as a society&quot; to find a way to curb gun violence. Over 100 attendees expected to see latest version of Microsoft Dynamics SL and Dynamics GP (PRWeb February 29, 2016) Read the full story at http://www.prweb.com/releases/2016/03/prweb13238571.htm "
  • Lower Case

Step pertama yang akan kita lakukan ada membuat data teks kita menjadi lower case, kali ini kita akan menggunakan fungsi str_to_lower() dari library stringr

result <- example %>% str_to_lower()
result
#> [1] "washington (ap) — president barack obama said thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in california and called on americans to &quot;search ourselves as a society&quot; to find a way to curb gun violence. over 100 attendees expected to see latest version of microsoft dynamics sl and dynamics gp (prweb february 29, 2016) read the full story at http://www.prweb.com/releases/2016/03/prweb13238571.htm "
  • Remove HTML Entities

Seperti yang kita temukan pada tahap EDA, data kita memiliki HTML entity yang perlu kita hilangkan. Untuk menghilangkan nya kita akan menggunan fungsi str_replace_all() dengan pattern yang sudah tersimpan pada variabel html_pattern.

result <- result %>% str_replace_all(html_pattern, " ")
result
#> [1] "washington (ap) — president barack obama said thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence. over 100 attendees expected to see latest version of microsoft dynamics sl and dynamics gp (prweb february 29, 2016) read the full story at http://www.prweb.com/releases/2016/03/prweb13238571.htm "
  • Remove URL

Pada URL akan kita hilangkan juga menggunakan fungsi str_replace_all() dengan pattern url_pattern.

result <- result %>% str_replace_all(url_pattern, " ")
result
#> [1] "washington (ap) — president barack obama said thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence. over 100 attendees expected to see latest version of microsoft dynamics sl and dynamics gp (prweb february 29, 2016) read the full story at   "
  • Replace Word Elongation

Pada step ini kita ingin merubah kata yang terjadi elongation atau pemanjangan seperti ‘waiiit’ akan di ganti menjadi ‘wait’. Walaupun pada data news berisi informasi berita yang biasanya sudah berbentuk bahasa formal tetapi tetap ada kemungkinan ada human error pada teks tersebut. Untuk melakukan hal tersebut kita dapat menggunakan fungsi replace_word_elongation() dari library textclean.

result <- result %>% replace_word_elongation()
result
#> [1] "washington (ap) — president barack obama said thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence. over 100 attendees expected to see latest version of microsoft dynamics sl and dynamics gp (prweb february 29, 2016) read the full story at   "
  • Replace Contraction

Sedangkan pada step ini, kita ingin merubah sebuah kata singkatan seperti ‘w8’ menjadi ‘wait’. Langkah ini merupakan langkah preventif untuk menghindari human error pada data teks. Kita dapat menggunakan fungsi replace_contraction().

result <- result %>%  replace_contraction() 
result
#> [1] "washington (ap) — president barack obama said thursday investigators are continuing to search for the motives of the people who killed 14 people in a spray of + bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence. over 100 attendees expected to see latest version of microsoft dynamics sl and dynamics gp (prweb february 29, 2016) read the full story at   "
  • Remove Numerical

Selanjutnya kita akan menghilangkan angka, dikarenakan pada classfication machine learning akan melakukan klasifikasi berdasarkan kata. Untuk menghilangkan angka kita dapat menggunakan fungsi str_replace_all().

result <- result %>% str_replace_all("[[:digit:]]", " ")
result
#> [1] "washington (ap) — president barack obama said thursday investigators are continuing to search for the motives of the people who killed    people in a spray of + bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence. over     attendees expected to see latest version of microsoft dynamics sl and dynamics gp (prweb february   ,     ) read the full story at   "
  • Remove Punctuation

Kita juga perlu untuk menghilangkan tanda baca, dikarenakan tidak akan digunakan dalam pembuatan model. Untuk menghilangkannya kita akan menggunakan kembali fungsi str_replace_all().

result <- result %>% str_replace_all("[[:punct:]]", " ")
result
#> [1] "washington  ap    president barack obama said thursday investigators are continuing to search for the motives of the people who killed    people in a spray of + bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence  over     attendees expected to see latest version of microsoft dynamics sl and dynamics gp  prweb february           read the full story at   "
  • Remove Symbol

Jika kita lihat pada hasil diatas, data tersebut masih memiliki sebuah symbol + yang harus kita hilangkan. Untuk menghilangkan nya kita akan menggunakan fungsi replace_symbol() dari library textclean.

result <- result %>% replace_symbol()
result
#> [1] "washington  ap    president barack obama said thursday investigators are continuing to search for the motives of the people who killed    people in a spray of   bullets in california and called on americans to  search ourselves as a society  to find a way to curb gun violence  over     attendees expected to see latest version of microsoft dynamics sl and dynamics gp  prweb february           read the full story at   "
  • Remove Stop Words

Selanjutnya kita akan menghapus stopword atau kata kata sambung yang sering muncul dan biasanya tidak meaningful. List stopword yang kita akan hilangkan didapatkan dari fungsi stopwords() dari library tm, lalu akan dijadikan sebuah pattern regex. Untuk menghilangkannya kita akan menggunakan fungsi str_replace_all().

stopwords_regex <- paste(stopwords('en'), collapse = '\\b|\\b')
stopwords_regex <- paste0('\\b', stopwords_regex, '\\b')

result <- result %>% str_replace_all(stopwords_regex, ' ')
result
#> [1] "washington  ap    president barack obama said thursday investigators   continuing   search     motives     people   killed    people     spray     bullets   california   called   americans    search       society    find   way   curb gun violence        attendees expected   see latest version   microsoft dynamics sl   dynamics gp  prweb february           read   full story     "
  • Stem Word

Setelah membersihkan stopword kita kan melakukan pemotong kata menjadi kata dasarnya atau stemming. Misalnya walking, walked, walks menjadi walk. Kita akan menggunakan fungsi stemDocument() dari library tm.

result <- stemDocument(result, language = 'english')
result
#> [1] "washington ap presid barack obama said thursday investig continu search motiv peopl kill peopl spray bullet california call american search societi find way curb gun violenc attende expect see latest version microsoft dynam sl dynam gp prweb februari read full stori"
  • Remove Whitespace

Langkah terakhir adalah menghapus whitespace berlebih. hal ini diperlukan karena pada proses tokenizing (selanjutnya), kata akan dipotong berdasarkan karakter spasi. Untuk melakukan hal tersebut kita akan menggunakan fungsi `replace_white()``

result <- result %>% replace_white()
result 
#> [1] "washington ap presid barack obama said thursday investig continu search motiv peopl kill peopl spray bullet california call american search societi find way curb gun violenc attende expect see latest version microsoft dynam sl dynam gp prweb februari read full stori"

Dari langkah-langkah text cleansing diatas, akan kita jadikan sebuah fungsi bernama text_cleansing agar memudahkan kedepannya.

text_cleansing <- function(text){
  # create pattern
  html_pattern <- '&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});'
  url_pattern <- 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
  stopwords_regex <- paste(stopwords('en'), collapse = '\\b|\\b')
  stopwords_regex <- paste0('\\b', stopwords_regex, '\\b')

  # cleansing
  new_data <- text %>%
      str_to_lower() %>%
      str_replace_all(html_pattern, ' ') %>%
      str_replace_all(url_pattern, " ") %>%
      replace_word_elongation() %>%
      replace_contraction() %>%
      str_replace_all("[[:digit:]]", " ") %>%
      str_replace_all("[[:punct:]]", " ") %>%
      replace_symbol() %>% 
      str_replace_all(stopwords_regex, ' ') %>%
      stemDocument(language = 'english') %>%
      replace_white()

  return(new_data)
}

Selanjutnya kita akan menggunakan fungsi text_cleansing yang sudah dibuat untuk diaplikasikan pada data news.

news_clean <- news %>% 
  mutate(
    Title = Title %>% text_cleansing(),
    Headline = Headline %>% text_cleansing()
  ) 

Berikut adalah 50 baris teratas hasil dari text cleansing pada data news.

head(news_clean,50)

Jika kita lihat pada kolom Title baris 14 dan 15 terdapat simbol £, dan pada kolom Headline baris 50 terdapat simbol ® dan ™. Maka dari kita harus menambahkan fungsi str_replace_all() pada fungsi text_cleansing untuk menghapus simbol-simbol tersebut.

text_cleansing <- function(text){
  # create pattern
  html_pattern <- '&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});'
  url_pattern <- 'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
  stopwords_regex <- paste(stopwords('en'), collapse = '\\b|\\b')
  stopwords_regex <- paste0('\\b', stopwords_regex, '\\b')
  other_symbol_pattern <-  "[^A-Za-z_0-9]"

  # cleansing
  new_data <- text %>%
      str_to_lower() %>%
      str_replace_all(html_pattern, ' ') %>%
      str_replace_all(url_pattern, " ") %>%
      replace_word_elongation() %>%
      replace_contraction() %>%
      str_replace_all("[[:digit:]]", " ") %>%
      str_replace_all("[[:punct:]]", " ") %>%
      replace_symbol() %>% 
      str_replace_all(other_symbol_pattern, " ") %>% 
      str_replace_all(stopwords_regex, ' ') %>%
      stemDocument(language = 'english') %>%
      replace_white()

  return(new_data)
}

Lalu kita aplikasikan kembali pada data news

news_clean <- news %>% 
  mutate(
    Title = Title %>% text_cleansing(),
    Headline = Headline %>% text_cleansing()
  ) 

head(news_clean,50)

Cross-Validation

Setelah dilakukan text cleansing, kita akan melakukan cross-validation pada data news_clean dengan pembagian 80% untuk data train dan 20% data test.

set.seed(121)

row_data <- nrow(news_clean)
index <- sample(row_data, row_data*0.8)

news_train <- news[index,]
news_test <- news[-index,]

Kita juga akan memisahkan antara data Title dan Headline yang kedepannya akan dibuatkan model terpisah.

title_train <- news_train %>% select(Title, SentimentTitleLable)
title_test <- news_test %>% select(Title, SentimentTitleLable)

headline_train <- news_train %>% select(Headline, SentimentHeadlineLable)
headline_test <- news_test %>% select(Headline, SentimentHeadlineLable)

Class Imbalance

Seperti yang kita ketahui pada tahap EDA, data news memiliki proporsi kelas yang tidak seimbang. Maka dari itu kita perlu melakukan downsampling agar proporsi kelas nya seimbang yang akan berpengaruh terhadap model kita.

Title Data

Berikut adalah proporsi kelas awal pada data title_train.

title_train$SentimentTitleLable %>%
  table() %>% 
  prop.table()
#> .
#>  negative   neutral  positive 
#> 0.4084926 0.2000670 0.3914404

Untuk mengetahui jumlah observasi awal kita dapat menggunakan fungsi nrow().

nrow(title_train)
#> [1] 44745

Fungsi yang digunakan untuk melakukan downsampling adalah downSample dari library caret.

set.seed(121)
title_train <- downSample(x = title_train$Title,
                          y = title_train$SentimentTitleLable,
                          yname = 'SentimentTitleLable')

title_train$SentimentTitleLable %>%
  table() %>% 
  prop.table()
#> .
#>  negative   neutral  positive 
#> 0.3333333 0.3333333 0.3333333

Jika dilihat dari proporsi kelas setelah dilakukan downsampling sudah seimbang, tetapi hal tersebut akan berpengaruh pada jumlah data nya.

nrow(title_train)
#> [1] 26856

Headline Data

Berikut adalah proporsi kelas awal pada data headline_train.

headline_train$SentimentHeadlineLable %>% 
  table() %>% 
  prop.table()
#> .
#>   negative    neutral   positive 
#> 0.57192982 0.03034976 0.39772042

Jumlah observasi awal dapat dilihat menggunakan fungsi nrow().

nrow(headline_train)
#> [1] 44745

Kita akan menggunakan fungsi downSample() kembali untuk melakukan downsampling pada data headline_train.

set.seed(121)
headline_train <- downSample(x = headline_train$Headline,
                             y = headline_train$SentimentHeadlineLable,
                             yname = 'SentimentHeadlineLable')

headline_train$SentimentHeadlineLable %>% 
  table() %>% 
  prop.table()
#> .
#>  negative   neutral  positive 
#> 0.3333333 0.3333333 0.3333333

Berikut adalah jumlah observasi setelah dilakukan downsampling :

nrow(headline_train)
#> [1] 4074

Text to Corpus

Corpus adalah kumpulan dari dokumen. Pada kasus ini, satu dokumen ekuivalen dengan satu observasi Title/Headline. Di dalam satu Title/Headline bisa terdapat satu atau lebih kalimat. Salah satu library yang bisa kita gunakan untuk text mining adalah tm. Pengubahan dari vector text menjadi corpus bisa dilakukan menggunakan fungsi VCorpus().

title_train_corpus <- VCorpus(VectorSource(title_train$x))
headline_train_corpus <- VCorpus(VectorSource(headline_train$x))

Document Term Matrix

Kita perlu melakukan transformasi data text menjadi Document-Term Matrix (DTM) melalu proses tokenization. Tokenization adalah proses memecah satu kalimat menjadi beberapa term (bisa berupa 1 kata, pasangan kata, dll). Dalam DTM, 1 kata akan menjadi 1 prediktor dengan nilai berupa frekuensi kemunculan kata tersebut dalam sebuah dokumen. Untuk melakukan transformasi tersebut kita dapat menggunakan fungsi DocumentTermMatrix() dari library tm.

title_train_dtm <- DocumentTermMatrix(title_train_corpus)
headline_train_dtm <- DocumentTermMatrix(headline_train_corpus)

Selanjutnya kita akan melakukan filter kata yang setidaknya muncul 10 kali pada Document-Term Matrix untuk memperingkas waktu komputasi.

freq_title <- findFreqTerms(title_train_dtm, 10)
freq_headline <- findFreqTerms(headline_train_dtm, 10)
length(freq_title)
#> [1] 2810
length(freq_headline)
#> [1] 1259
title_train_dtm <- title_train_dtm[, freq_title]
headline_train_dtm <- headline_train_dtm[, freq_headline]

Bernoulli Converter

Pada matrix title_train_dtm dan headline_train_dtm masih berupa frekuensi. Untuk perhitungan peluang, frekuensi akan diubah menjadi hanya kondisi muncul (1) atau tidak (0). Salah satu caranya dengan menggunakan bernoulli converter.

bernoulli_conv <- function(x){
        x <- factor(
          ifelse(x > 0, 1, 0), levels = c(0,1), labels = c("Absent", "Present")
          )
        return(x)}

# convert the document-term matrix
title_train_x <- apply(title_train_dtm, 2, bernoulli_conv)
headline_train_x <-  apply(headline_train_dtm, 2, bernoulli_conv)


# create the target variable
title_train_label <- title_train$SentimentTitleLable
headline_train_label <- headline_train$SentimentHeadlineLable 
 

A work by Muhammad Yusuf Ibrahim

myusufibrahim@outlook.com