gensimを用いたTF-IDFの実装

[前回]()は文春オンラインの記事をスクレイピングして、ネガポジ分析を行いました。
今回は文春オンラインの記事の内容に対して`gensim`を使って`TF-IDF`を行い、重要単語を抽出してみようと思います！

## 流れ
1. 参考記事
2. TF-IDFとは
3. 形態素解析
4. DF-IDFを実行する前の準備
5. TF-IDFの実装
6. TF-IDFで上位の単語を出力

## 参考記事
TF-IDFについて　　　　　　https://mieruca-ai.com/ai/tf-idf_okapi-bm25/
gensimのtfidfについて　　　https://qiita.com/tatsuya-miyamoto/items/f1539d86ad4980624111
## TF-IDFとは
TF-IDFが高い場合は、その文章を特徴付けるような単語となり、
それほど重要ではない単語と言うことができると思います。  
例えば、スポーツ新聞をみており、一つの記事に注目してその記事の文章に「ホームラン」と言う単語があれば、
この記事の内容は野球についてである、とすぐに分かります。
このような場合は野球と言う単語のTF-IDFは高い値を示す可能性が高いと思います。
一方、ある記事の文章に「選手」と言う単語が入っていたとしましょう。
スポーツ新聞なので、おそらくどの記事にも多く使われていることでしょう。
このような場合、選手と言う単語のTF-IDFは低いと思います。  
詳しくは[こちら](https://mieruca-ai.com/ai/tf-idf_okapi-bm25/)を参考にしてください。

今回はTF-IDFを使って記事ごとの重要な単語を抽出してみます。
それでは早速作業してみましょう。
まずは必要なライプラリーを読み込みます。


```python
from gensim import corpora
from gensim import models
from janome.tokenizer import Tokenizer
from pprint import pprint
import pandas as pd
import logging
```

前回の記事で作成した文春オンラインをCSVファイルとして保存していたので、そちらを利用します。


```python
df = pd.read_csv('/work/data/BunshuOnline/news.csv')
doc_list = list(df.news_page_list)
```

記事の内容を確認してみましょう。


```python
for i in range(10):
    print('%s.' % i, '〜%s〜' % df['title'][i])
    print(df['news_page_list'][i][:200], end='\n\n')
```

    0. 〜波乱はルーキーたちの走り次第？ “怪物世代”の初陣に注目 第97回箱根駅伝予選会を読む〜
    「例年なら『駅伝はトラックとは別物だから、1年生は戦力としてあまり期待しすぎないほうがいい』という話が出るんです。でも、今年はちょっと雰囲気が違いますね」 そんな風に今季の驚きを語るのは、スポーツ紙の駅伝担当記者だ。 春先から続くコロナ禍の中で、今年はここまでスポーツ界も大きな影響を受けてきた。それは学生長距離界においても同様で、春から夏にかけて大会の中止はもちろん、記録会や各校の練習にも大きな支
    
    1. 〜スマホやタブレットじゃやっぱり不十分？ 「オンライン授業」に最適のPCとは〜
     2020年は、まさにデジタル教育の歴史にとって特記されるべき重要な年となった。まずは、小学校においてプログラミング教育が必修化された。そして、予期せぬコロナ禍の到来によって、デジタル機器を使っての学習は不可避のものとなった。 コロナで家族そろって自宅に籠っていた間、親はリモートワーク、子どもはオンライン学習に励むため、家に1台しかないPCを親子で取り合うことになった経験を持つ読者は多いだろう。 
    
    2. 〜幼児教育「ヨコミネ式」創設者・横峯吉文氏のDVを元妻と子供が告白《法廷闘争“全面対決”へ》〜
     保育園児が跳び箱10段を跳び、倒立歩行。さらに九九算も覚え、漢字を読み書きし、3年間で平均2000冊の本を読破する――。「すべての子供が天才である」をモットーに、鹿児島から全国に広がった独創的な教育方法がある。子供の向上心や競争心に火を付け、強制することなく自分で学ぶ力を伸ばすこの教育法は「ヨコミネ式」と呼ばれている。いまでは、全国で約400もの保育園・幼稚園が、このヨコミネ式カリキュラムを導入
    
    3. 〜BTS会社社長、韓国8位の株式富豪に…「共通点はオタクなこと」J.Y.Parkが語った素顔〜
     上場前から大騒ぎだった。 10月15日、「防弾少年団（BTS）」の所属事務所「Big Hit Entertainment（BHエンタテインメント、以下BH社）」がKOSPI（韓国取引所）に上場した。初値は公募価格の2.6倍をつけ、時価総額は8兆8169億ウォン（約8800億円同日15時時点）。韓国の3大音楽事務所の時価総額を合わせたものをはるかに超え、韓国最大手の音楽事務所に躍り出た。 創業者、
    
    4. 〜「レジ袋はいりません」からの流れでびっくりするほど挙動不審になった話〜
     漫画家の山本さほさんが、厄介な人たちを引き寄せるトラブル続きな日々をつづります。今回は、薬局とスーパーで買い物をしたときのお話です。毎週木曜日更新。 最近更新が遅れ気味ですが、次回は10月22日（木）公開予定です（たぶん）。『きょうも厄日です』の単行本が好評発売中です。山本さんのハプニングが詰まった第1巻は、大炎上したあの区役所との“闘い”を大幅描きおろし!!きょうも厄日です 1山本 さほ 文藝
    
    5. 〜三浦春馬さん、竹内結子さん、木村花さん…芸能人「自殺の連鎖」はなぜ起きるのか？〜
     三浦春馬さんが亡くなってから3カ月が経とうとしているが、いまだに彼の自死をめぐる報道は止まない。 三浦さんだけではない。リアリティー番組「テラスハウス」に出演していた木村花さん、芦名星さん、藤木孝さん、竹内結子さんなど、芸能人の死をめぐる報道が「次なる死」を招きかねないと警鐘を鳴らすのが、筑波大学教授の斎藤環氏だ。「大変言いにくいことですが、精神科で患者が自死すると、同じように死を選ぶ患者が続発
    
    6. 〜内部資料入手「GoToトラベル事務局」大手出向社員に日当4万円〜
     10月1日から東京発着の旅行も対象に加わった政府の観光支援策「GoToトラベル事業」。その運営を担う「GoToトラベル事務局」に出向している大手旅行代理店社員に、国から高額な日当が支払われていることが、「週刊文春」の取材でわかった。 GoToトラベル事務局を構成するのは、全国旅行業協会（ANTA）などを除けば、業界最大手のJTBを筆頭に、近畿日本ツーリストを傘下に置くKNT－CTホールディングス
    
    7. 〜「お前は何アゲだ？ 何で俺だけ“たたき上げ”なんだ！」“最後の怪物幹事長”二階俊博がキレた瞬間〜
     自民党幹事長の通算在職記録を更新中の二階俊博は寝業師、すなわち「政治技術の巧者」という報道のされ方をすることが多い。政策というと親中派であること、運輸や土地改良事業の族議員であることぐらいは知られるが、政治思想は意外と知られていない。メディアで多くを語る姿を見ない二階が今回、インタビューで「政治の原点」を語った。 与えられた時間は30分間、限られた時間の中で、私は二階にいくつもの質問を矢継ぎ早に
    
    8. 〜「バラバラにした遺体を鍋で……」白石隆浩被告が証言した、おぞましい犯行の一部始終〜
    「背後に回って、首を左腕で絞めた」 2017年10月に神奈川県座間市のアパートで男女9人の遺体が見つかった事件で、強盗・強制性交殺人の罪に問われている白石隆浩被告（30）の裁判員裁判が、東京地裁立川支部（矢野直邦裁判長）で10月14日にあった。この日は、3人目に殺害したCさん（当時20、男性）の事件に関しての被告人質問が行われた。白石被告は、Cさんは殺害されることに承諾していなかったといい、殺害時
    
    9. 〜「長さ」ではなく…美容師が教える、髪を切るときに言うといい「意外な言葉」〜
     掛け布団がだんだん心地よくなってきた、今日この頃。朝夜の急激な気温差に、薄めの上着を羽織ったり、そろそろヘアスタイルも秋冬仕様に変えようかな、と思っている貴方へ。 いざ予約をして実際に美容師さんと顔を合わせたところで、希望をどう伝えればいいのかわからなくなっちゃう、そんなことはありませんか？ 今回は、どうすれば美容師さんに伝わるのか？ のお話です。 よくあるお客様のオーダーの例をあげていきます。
    


## 形態素解析
形態素解析を行います。
`gensim`の`Tokenizer()`を用いることで簡単に実装することができます。
記号は必要ないので、`part_of_speech`を用いて除外します。


```python
t = Tokenizer()
wakati_list = []
for doc in doc_list:
    tokens = t.tokenize(doc)
    wakati = []
    for token in tokens:
        if token.part_of_speech.split(',')[0] not in ['記号']:
            wakati.append(token.base_form)
    wakati_list.append(wakati)
```

分かち書きと記号の除去がうまくできたか確認してみましょう。


```python
for wakati in wakati_list:
    print(wakati[:10])
```

    ['例年', 'だ', '駅伝', 'は', 'トラック', 'と', 'は', '別物', 'だ', 'から']
    ['2020', '年', 'は', 'まさに', 'デジタル', '教育', 'の', '歴史', 'にとって', '特記']
    ['保育園', '児', 'が', '跳び箱', '10', '段', 'を', '跳ぶ', '倒立', '歩行']
    ['上場', '前', 'から', '大騒ぎ', 'だ', 'た', '10', '月', '15', '日']
    ['漫画', '家', 'の', '山本', 'さ', 'ほす', 'ん', 'が', '厄介', 'だ']
    ['三浦', '春', '馬', 'さん', 'が', '亡くなる', 'て', 'から', '3', 'カ月']
    ['10', '月', '1', '日', 'から', '東京', '発着', 'の', '旅行', 'も']
    ['自民党', '幹事', '長', 'の', '通算', '在職', '記録', 'を', '更新', '中']
    ['背後', 'に', '回る', 'て', '首', 'を', '左腕', 'で', '絞める', 'た']
    ['掛け布団', 'が', 'だんだん', '心地よい', 'なる', 'て', 'くる', 'た', '今日', 'この']


## DF-IDFを実行する前の準備

次に`gensim`の`corpora`を使って単語にIDを添付していきます。  


```python
dictionary = corpora.Dictionary(wakati_list)
print('==={単語: ID}===')
pprint(dictionary.token2id)
```

    


    ==={単語: ID}===
    {'!': 1021,
     '!!': 1822,
     ',': 2023,
     '-': 568,
     '.': 569,
     ~~~~<省略>~~~~~
     'なんて': 1084,
     'なんで': 1085,
     'なんとなく': 2693,
     'なー': 2694,
     'に': 104,
     ~~~~<省略>~~~~~
     'メモアプリ': 2497,
     'メンタル': 1890,
     'メンバー': 174,
     'メーカー': 742,
     'モダン': 743}


次に単語ごとの記事ごとの出現回数をカウントします。


```python
corpus = list(map(dictionary.doc2bow, wakati_list))
print('===(単語ID, 出現回数)===')
pprint(corpus)
```

    ===(単語ID, 出現回数)===
    [[(0, 10),
      (1, 5),
      (2, 1),
      (3, 1),
      (4, 1),
      ~~~~<省略>~~~~~
      (2872, 1),
      (2873, 1),
      (2874, 2),
      (2875, 1),
      (2876, 1)]]

## TF-IDFの実装
次に出現回数のデータを用いていよいよTF-IDFを算出します。
`gensim`の`models`を利用します。
`gensim`は本当にたくさんの機能がありますね。
計算結果の一部を表示させます。


```python
test_model = models.TfidfModel(corpus)
corpus_tfidf = test_model[corpus]
print('===(単語ID, TF-IDF)===')
for doc in corpus_tfidf:
    print(doc[:4])
```

    ===(単語ID, TF-IDF)===
    [(0, 0.010270560215244124), (1, 0.010876034850944512), (2, 0.011736346492935309), (3, 0.006756809998052433)]
    [(0, 0.0042816896254018535), (3, 0.005633687484063879), (5, 0.008303667688079712), (7, 0.005633687484063879)]
    [(0, 0.001569848428761509), (1, 0.006649579327355055), (3, 0.005163870001530652), (5, 0.00761118904775017)]
    [(0, 0.004119674666568976), (1, 0.006543809340846026), (3, 0.006775642806339103), (5, 0.02496707813315839)]
    [(0, 0.01276831026581211), (1, 0.013521033373868814), (7, 0.04200016584013773), (13, 0.04200016584013773)]
    [(1, 0.007831949836845296), (2, 0.0422573475812842), (7, 0.024328258270175436), (11, 0.00625933452375299)]
    [(0, 0.00115918318434994), (1, 0.004910079468851687), (9, 0.013246186396066643), (11, 0.0039241607229361)]
    [(0, 0.0014966665107978136), (1, 0.006339594643539755), (2, 0.06841066655360874), (3, 0.009846289814745559)]
    [(0, 0.0026696482016164125), (1, 0.003769373987012683), (5, 0.004314471125835394), (6, 0.007739059517798651)]
    [(0, 0.0011200718719816475), (25, 0.020161293695669654), (26, 0.06631869535917725), (27, 0.0037917579432113335)]


単語IDでは分かりにくいので、単語を表示します。


```python
texts_tfidf = []
for doc in corpus_tfidf:
    text_tfidf = []
    for word in doc:
        text_tfidf.append([dictionary[word[0]], word[1]])
    texts_tfidf.append(text_tfidf)
print('===[単語, TF-IDF]===')
for i in texts_tfidf:
    print(i[20:24])
```

    ===[単語, TF-IDF]===
    [['U', 0.022445636964346205], ['m', 0.11222818482173101], ['あくまで', 0.022445636964346205], ['あそこ', 0.022445636964346205]]
    [['しっかり', 0.02616203449412644], ['しまう', 0.002898944443406048], ['じゃ', 0.004151833844039856], ['せる', 0.005797888886812096]]
    [['これ', 0.0026571889707680363], ['さらに', 0.013652529666738833], ['しまう', 0.010628755883072145], ['じゃ', 0.003805594523875085]]
    [['ここ', 0.006775642806339103], ['こと', 0.008239349333137951], ['これ', 0.0034865640168190394], ['さて', 0.015732555392960215]]
    [['記事', 0.006384155132906055], ['家', 0.04200016584013773], ['最近', 0.07295284301359403], ['あの', 0.09752136505414427]]
    [['て', 0.053620572470353435], ['できる', 0.01792908931110876], ['です', 0.03132779934738118], ['でも', 0.0018489852575983943]]
    [['その', 0.0034775495530498207], ['そもそも', 0.010081089692211678], ['て', 0.020865297318298923], ['です', 0.019640317875406748]]
    [['じゃ', 0.007256376823656625], ['すぎる', 0.017102666638402184], ['せる', 0.005066636590574954], ['そう', 0.01451275364731325]]
    [['しまう', 0.009037509134257363], ['じゃ', 0.004314471125835394], ['せる', 0.018075018268514726], ['そう', 0.012943413377506183]]
    [['そう', 0.005430510747744854], ['その', 0.006720431231889886], ['そもそも', 0.00974094962350806], ['そんな', 0.01948189924701612]]

## TF-IDFで上位の単語を出力
それではいよいよTF-IDFで上位の単語を記事ごとに表示してみましょう！
今回はTF-IDFが0.1以上の単語のみ表示させてみます！


```python
for i in range(len(texts_tfidf)):
    print('')
    print('%s.' % i, '〜%s〜' % df['title'][i])
    for text in texts_tfidf[i]:
        if text[1] > 0.13:
            print(text)
```

    
    0. 〜波乱はルーキーたちの走り次第？ “怪物世代”の初陣に注目 第97回箱根駅伝予選会を読む〜
    ['ルーキー', 0.29179328053650067]
    ['予選', 0.26934764357215446]
    ['会', 0.2353324044944065]
    ['年生', 0.13467382178607723]
    ['校', 0.17956509571476964]
    ['監督', 0.13467382178607723]
    ['練習', 0.15711945875042344]
    ['走り', 0.15711945875042344]
    ['駅伝', 0.3591301914295393]
    ['高校', 0.14119944269664392]
    
    1. 〜スマホやタブレットじゃやっぱり不十分？ 「オンライン授業」に最適のPCとは〜
    ['.', 0.17005322421182187]
    ['/', 0.2807205709669065]
    ['LAVIE', 0.1684323425801439]
    ['Office', 0.1310029331178897]
    ['PC', 0.561441141933813]
    ['子ども', 0.2092962759530115]
    ['学習', 0.14971763784901682]
    ['搭載', 0.1684323425801439]
    ['機能', 0.1684323425801439]
    
    2. 〜幼児教育「ヨコミネ式」創設者・横峯吉文氏のDVを元妻と子供が告白《法廷闘争“全面対決”へ》〜
    ['教育', 0.15248089693189756]
    ['A', 0.1435114324064918]
    ['ミネ', 0.1715400483643072]
    ['ヨコ', 0.1715400483643072]
    ['保育園', 0.37738810640147585]
    ['吉文', 0.3259260918921837]
    ['子', 0.2744640773828915]
    ['子供', 0.2573100725464608]
    ['暴力', 0.2230020628735994]
    ['氏', 0.17938929050811475]
    ['祖母', 0.13723203869144576]
    ['鹿児島', 0.13723203869144576]
    
    3. 〜BTS会社社長、韓国8位の株式富豪に…「共通点はオタクなこと」J.Y.Parkが語った素顔〜
    ['.', 0.14159299853664192]
    ['BH', 0.20257378379369387]
    ['BoA', 0.13504918919579592]
    ['JYP', 0.13504918919579592]
    ['SM', 0.13504918919579592]
    ['アーティスト', 0.13504918919579592]
    ['事務所', 0.27009837839159184]
    ['億', 0.13504918919579592]
    ['社', 0.28318599707328385]
    ['韓国', 0.40514756758738774]
    
    4. 〜「レジ袋はいりません」からの流れでびっくりするほど挙動不審になった話〜
    ['更新', 0.14590568602718806]
    ['!!', 0.13952153089428201]
    ['22', 0.13952153089428201]
    ['おろす', 0.13952153089428201]
    ['きょう', 0.27904306178856403]
    ['たぶん', 0.13952153089428201]
    ['つづる', 0.13952153089428201]
    ['ほす', 0.13952153089428201]
    ['ほる', 0.13952153089428201]
    ['トラブル', 0.13952153089428201]
    ['ハプニング', 0.13952153089428201]
    ['区役所', 0.13952153089428201]
    ['単行本', 0.13952153089428201]
    ['厄介', 0.13952153089428201]
    ['厄日', 0.27904306178856403]
    ['大幅', 0.13952153089428201]
    ['好評', 0.13952153089428201]
    ['山本', 0.41856459268284607]
    ['巻', 0.13952153089428201]
    ['引き寄せる', 0.13952153089428201]
    ['木曜日', 0.13952153089428201]
    ['次回', 0.13952153089428201]
    ['毎週', 0.13952153089428201]
    ['漫画', 0.13952153089428201]
    ['炎上', 0.13952153089428201]
    ['薬局', 0.13952153089428201]
    ['詰まる', 0.13952153089428201]
    ['買い物', 0.13952153089428201]
    ['闘い', 0.13952153089428201]
    
    5. 〜三浦春馬さん、竹内結子さん、木村花さん…芸能人「自殺の連鎖」はなぜ起きるのか？〜
    ['さん', 0.14596954962105263]
    ['投影', 0.16163344929474321]
    ['木村', 0.16163344929474321]
    ['死', 0.4849003478842297]
    ['自殺', 0.14790071653449471]
    ['芸能人', 0.25419809869738275]
    
    6. 〜内部資料入手「GoToトラベル事務局」大手出向社員に日当4万円〜
    ['旅行', 0.1593642568499776]
    [',', 0.20266551686226678]
    ['GoTo', 0.32933146490118353]
    ['トラベル', 0.20266551686226678]
    ['事務', 0.3546646545089669]
    ['出向', 0.1519991376467001]
    ['大手', 0.17733232725448345]
    ['局', 0.3546646545089669]
    ['技師', 0.17733232725448345]
    ['技術', 0.17707139649997508]
    ['日当', 0.25333189607783346]
    ['社員', 0.17733232725448345]
    
    7. 〜「お前は何アゲだ？ 何で俺だけ“たたき上げ”なんだ！」“最後の怪物幹事長”二階俊博がキレた瞬間〜
    ['二', 0.25653999957603274]
    ['階', 0.3200732773176539]
    ['国土', 0.13717426170756597]
    ['俊博', 0.13083466706402622]
    ['原点', 0.13083466706402622]
    ['均衡', 0.16354333383003275]
    ['政治', 0.26166933412805243]
    ['発展', 0.22896066736204587]
    ['県議', 0.13083466706402622]
    ['秘書', 0.16354333383003275]
    ['道路', 0.22896066736204587]
    
    8. 〜「バラバラにした遺体を鍋で……」白石隆浩被告が証言した、おぞましい犯行の一部始終〜
    ['さん', 0.2575923910688616]
    ['C', 0.5029569855573655]
    ['殺害', 0.3889560913276654]
    ['白石', 0.3695082867612821]
    ['被告', 0.3889560913276654]
    
    9. 〜「長さ」ではなく…美容師が教える、髪を切るときに言うといい「意外な言葉」〜
    ['師', 0.23953570973227745]
    ['お客様', 0.19582749984882405]
    ['ヘア', 0.14687062488661806]
    ['ヘアスタイル', 0.44061187465985413]
    ['美容', 0.44061187465985413]


`gensim`に便りっきりの簡単な実装でしたが、ある程度重要そうな単語のみを抽出できているのではないでしょうか？
`gensim`は非常に便利であることが分かりました。

次回は`TF-IDF`と`word2vec`を用いて、テーマを決めてクラスタリングを実装してみようかと思います！

→　厳密にはクラスタリングでは無いのですが、記事にしました！よかったら読んでください！[青空文庫の書籍をDoc2Vecでクラスタリング](https://qiita.com/kei0919/items/bde365bf179c0a1573af)