SudachiPyで形態素単位に分割｜つぶやきをWordcloudで可視化③

私事ではありますが、2020年5月にコーギーという中型犬をお迎えしました。
<b>[愛犬のブログ](https://corgi-komugi.com/)</b>を運営してますので、見てもらえると嬉しいです。

<img width="400" alt="corgi_komugi" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522259/1d07a01c-ce87-9262-c112-e8509ebb3a4e.jpeg">


<b>「コーギー関連のツイートをwordcloudで可視化したい！」</b>と思い立ち、以下の4項目を実施しました。

1. [TwitterAPIでツイートを取得](https://qiita.com/komugi_papa/items/e5d7fb6dafaf68caa1e0)
2. [正規表現を用いてツイートを整形](https://qiita.com/komugi_papa/items/9b21cc0b5c612b5c0521)
3. SudachiPyで形態素単位に分割 ← 今回
4. [WordCloudで可視化](https://qiita.com/komugi_papa/items/8b0cbe1f2674ed54040b)

⇒ <b>[一連のコードと様々なアウトプットの表示](https://corgi-komugi.com/twitter_wordcloud/)</b>

今回は、<b>「SudachiPyで形態素単位に分割」</b>に関して説明していきます。

## 対象者

- SudachiPyの特徴を知りたい人
- SudachiPyでコードを書いてみたい人

## SudachiPyの特徴

SudachiPyのメンテナーがこちらのリンクで書いてくれてます。

https://speakerdeck.com/waptech/ri-ben-yu-xing-tai-su-jie-xi-qi-sudachipy-false-xian-zhuang-tojin-hou-nituite


まとめると、特徴は以下の3つになります。

- small, core, fullの3タイプから、使用する辞書を選択できる
- 分割タイプも3タイプから選択できる
- 表記を正規化してくれる


##ざっくりとした要件定義

Input ... 前処理が完了した、DataFrame形式のツイートデータ
Output ... 単語の間をスペースで分割し、すべてのツイートを結合したstring

Inputは、こちらの記事で取得したデータフレーム(以下)になります。

![2021-07-13_06h26_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522259/057b766b-b1ed-7762-f3c1-6efadf91efc7.png)

Outputは、テキスト内容を形態素単位に分割し、次のようになります。

![2021-07-13_08h57_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522259/a0e0a528-2541-3f64-0f8b-bcf734a08ee5.png)

##コード
###コード全体

```python
import pandas as pd
from sudachipy import tokenizer
from sudachipy import dictionary

class SudachiTokenizer():
    def __init__(self, dict_type="core", mode="C", stopwords=None, include_pos=None):
        
        if dict_type not in ["core", "small", "full"]:
            raise Exception("invalid dict_type. 'core' ,'small' or 'full'")
        self.tokenizer_obj = dictionary.Dictionary(dict_type=dict_type).create()
        
        if mode not in ["A", "B", "C"]:
            raise Exception("invalid mode. 'A' ,'B' or 'C'")
        self.mode = getattr(tokenizer.Tokenizer.SplitMode, mode)
        print(self.mode )
        
        if stopwords is None:
            self.stopwords = []
        else:
            self.stopwords = stopwords
        
        if include_pos is None:
            self.include_pos = ["名詞", "動詞", "形容詞"]
        else:
            self.include_pos = include_pos
    
    def parser(self, text):
        return self.tokenizer_obj.tokenize(text, self.mode)
    
    def tokenize(self, text, pos=False):
        res = []
        for m in self.parser(text):
            p = m.part_of_speech()
            base = m.normalized_form() #.dictionary_form()
            #print(base, ": ", p)
            if p[0] in self.include_pos and base not in self.stopwords and p[1] != "数詞":
                if pos:
                    res.append((base, p[0]))
                else:
                    res.append(base)
        return res    

def create_word_chain(col, df, tokenizer):
    word_lists=[]
    
    for i in range(len(df)):
        text = df.loc[i, col]
        word_list = tokenizer.tokenize(text, pos=False)
        for word in word_list:
            word_lists.append(word)      
    word_chain =' '.join(word_lists)
    return word_chain

include_pos = ["名詞", "動詞", "形容詞"]
stopwords = ["コーギー", "見る","為る", "今日","無い","居る","成る"]
sudachi_tokenizer = SudachiTokenizer(dict_type="core", mode="A", stopwords=stopwords, include_pos=include_pos)
word_chain = create_word_chain('TW_TEXT_mod', df, sudachi_tokenizer)
```

###コード解説
#### SudachiTokenizer()のinit
SudachiPyで必要なdict_typeとmodeを指定します。
SudachiPyの条件に合わない語句を指定した場合は、エラーが出るようにしています。

今回の分析では、dict_typeを変更しても、最終的な結果に大きな変化はありませんでした。(~~もしかすると、うまくdict_typeを変更できてないかもです。~~)

また、stopwords(形態素解析のあとに、削除するワード)とinclude_pos(形態素解析のあとに、含める品詞)を指定します。

```python
class SudachiTokenizer():
    def __init__(self, dict_type="core", mode="C", stopwords=None, include_pos=None):
        
        if dict_type not in ["core", "small", "full"]:
            raise Exception("invalid dict_type. 'core' ,'small' or 'full'")
        self.tokenizer_obj = dictionary.Dictionary(dict_type=dict_type).create()
        
        if mode not in ["A", "B", "C"]:
            raise Exception("invalid mode. 'A' ,'B' or 'C'")
        self.mode = getattr(tokenizer.Tokenizer.SplitMode, mode)
        print(self.mode )
        
        if stopwords is None:
            self.stopwords = []
        else:
            self.stopwords = stopwords
        
        if include_pos is None:
            self.include_pos = ["名詞", "動詞", "形容詞"]
        else:
            self.include_pos = include_pos

```

####　SudachiTokenizerクラスのparserとtokenize関数

parser関数でparserを定義します。
> 結果はMorphemeのリストとなっており、表層形 (surface()) 、品詞 (part_of_speech()) 、読み (reading_form()) 、正規化した表現 (normalized_form()) を取得できます
(引用元：https://ohke.hateblo.jp/entry/2019/03/09/101500)

次に、tokenize関数で条件に合った語句を抽出し、リストに加えます。
今回は、正規化した表現で抽出したいので、normalized_form()を使用しています。


```python
    def parser(self, text):
        return self.tokenizer_obj.tokenize(text, self.mode)
    
    def tokenize(self, text, pos=False):
        res = []
        for m in self.parser(text):
            p = m.part_of_speech()
            base = m.normalized_form() #.dictionary_form()
            #print(base, ": ", p)
            if p[0] in self.include_pos and base not in self.stopwords and p[1] != "数詞":
                if pos:
                    res.append((base, p[0]))
                else:
                    res.append(base)
        return res
```

####create_word_chain関数
次の章でwordcloudで頻出語を可視化しますので、語句と語句の間をスペースで区切り、ひとつのstringにします。

全てのtweetに対して実施する必要があるので、ツイートごとに先ほど作成した関数で形態素単位に分割し、for文でそれを全ツイート繰り返します。

```python
def create_word_chain(col, df, tokenizer):
    word_lists=[]
    
    for i in range(len(df)):
        text = df.loc[i, col]
        word_list = tokenizer.tokenize(text, pos=False)
        for word in word_list:
            word_lists.append(word)      
    word_chain =' '.join(word_lists)
    return word_chain
```

####作成した関数の実行
これまで作成した関数を実行し、word_chainを作成します。
最終結果に「居る」や「成る」といったコーギーの特徴を表さない語句が表示されていたので、これらの語句をstopwordsに追加しています。

次の章のwordcloudでもstopwordsを指定でき、そちらで指定したほうが全体の計算量が少ないかもしれません。。
(~~大変そうですし、~~今回は計算量は無視しました。)

```
include_pos = ["名詞", "動詞", "形容詞"]
stopwords = ["コーギー", "見る","為る", "今日","無い","居る","成る"]
sudachi_tokenizer = SudachiTokenizer(dict_type="core", mode="A", stopwords=stopwords, include_pos=include_pos)
word_chain = create_word_chain('TW_TEXT_mod', df, sudachi_tokenizer)
```

##まとめ
この記事では、形態素解析器を用いて、ツイートを形態素に分割しました。

次は、ついにwordcloudで可視化します。
[こちらの記事](https://qiita.com/komugi_papa/items/8b0cbe1f2674ed54040b)で説明してますので、興味があれば見てもらえると嬉しいです。

<img width="400" alt="corgi_komugi" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522259/1b3f0447-f365-50ee-9e96-ac3134ffad25.jpeg">

##参考文献
[日本語形態素解析器 SudachiPy の 現状と今後について](https://speakerdeck.com/waptech/ri-ben-yu-xing-tai-su-jie-xi-qi-sudachipy-false-xian-zhuang-tojin-hou-nituite)
[GitHub - WorksApplications/Sudachipy](https://github.com/WorksApplications/Sudachipy)
[形態素解析器比較 Sudachi vs Mecab+Neologd](https://tdual.hatenablog.com/entry/2020/07/13/162151)
[Pythonで形態素解析器Sudachiを使う (SudachiPy)](https://ohke.hateblo.jp/entry/2019/03/09/101500)