寄せ書きクラウド：ワードクラウドで寄せ書きをつくる

# はじめに
3月、別れの季節ということでお世話になった方へ寄せ書きを渡すことがあります。
この記事では、ワードクラウドを使った<font color="silver">~~一風変わった~~</font>エンジニアらしい寄せ書きをつくる方法を紹介します。

# 完成形
寄せ書きのメッセージからワードクラウドを作成します。事前準備するイラストをマスク画像として使い、良い感じのデザインのものにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/ee886664-c7cc-9b3b-8d8c-f38a0d567f5e.png" width="720px"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/098f4f54-62d0-468c-4f58-9d2d14dc2caf.png" width="422px">

# 環境
- Google Colaboratory（Google Colab）

作成環境としては、Google Colabを利用して、Webブラウザ上で作業しました。

https://colab.research.google.com/notebooks/welcome.ipynb?hl=ja

# 事前準備
事前準備としては、まずは寄せ書きのメッセージとワードクラウドの出力領域の形をデザインするためのマスク画像を用意します。
これらのファイルは、Google Colabから使うために、Googleドライブに保存します。

## 寄せ書きのメッセージ
各人からの寄せ書きのメッセージを、以下のようにテキスト形式のファイルにまとめます。
```text:messages.txt
○○さん、定年退職おめでとうございます。
これからは大好きな趣味を存分に楽しんでください。

○○さん、在職中は色々とお世話になりました。
これからもお体を大切にいつまでもお元気で。
・・・
```

## マスク画像
以下のイラストをベースにさせていただき、マスク画像を作成しました。
- https://publicdomainq.net/heart-envelope-love-letter-0055143/
- https://publicdomainq.net/flower-bouquet-0013857/

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/e91e9acf-58ef-1eb3-0c05-e55b320f75d9.png" width="232px"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/8baa5463-598d-50f9-ff05-7ed6f7f6a761.png" width="400px">

# 寄せ書きのワードクラウド作成
## 必要ライブラリインストール
```python
!apt update
!apt install fonts-ipafont fonts-ipaexfont
!pip install mecab-python3 unidic-lite
```

[mecab-python3](https://github.com/SamuraiT/mecab-python3)のREADMEに従って、MeCab(形態素解析エンジン)のPythonライブラリと辞書、日本語フォントをインストールしました。
[wordcloud](https://github.com/amueller/word_cloud)とグラフ描画ライブラリの[matplotlib](https://matplotlib.org/)は既にインストールされていました。

インストールが完了したら、MeCabが使えることを次のコマンドで確認します。

```python
import MeCab
print(MeCab.Tagger().parse("すもももももももものうち"))
```

```
]
0 秒
import MeCab
print(MeCab.Tagger().parse("すもももももももものうち"))
すもも	スモモ	スモモ	李	名詞-普通名詞-一般			0
も	モ	モ	も	助詞-係助詞			
もも	モモ	モモ	桃	名詞-普通名詞-一般			0
も	モ	モ	も	助詞-係助詞			
もも	モモ	モモ	桃	名詞-普通名詞-一般			0
の	ノ	ノ	の	助詞-格助詞			
うち	ウチ	ウチ	内	名詞-普通名詞-副詞可能			0
EOS
```

## 寄せ書きメッセージとマスク画像の読み込み
Googleドライブに保存した寄せ書きのメッセージとマスク画像を読み込みます。
実行すると、Googleドライブ利用の許可を求められるので、問題のない場合はアクセスを許可します。

```python
from google.colab import drive
from PIL import Image
import numpy as np
# Googleドライブをマウント
drive.mount('/content/drive')

text = open('/content/drive/MyDrive/Colab/Yosegaki/messages.txt').read()
letter_mask = np.array(Image.open('/content/drive/MyDrive/Colab/Yosegaki/letter_mask.png'))
flower_mask = np.array(Image.open('/content/drive/MyDrive/Colab/Yosegaki/flower_mask.png'))
```

## 形態素解析
形態素解析の部分については、[参考記事](https://qiita.com/kiyokiyo_kzsby/items/554c571404f3f9844aaf#%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90)のコードを参考にさせていただきました。
引数に出力に含めない品詞の種類を指定可能なようにしています。

```python
import MeCab as mc

def mecab_analysis(text, exclude_type=[]):
    print("start mecab analysis")
    t = mc.Tagger()
    node = t.parseToNode(text)
    output = []
    while(node):
        if node.surface != "":
            word_type = node.feature.split(",")[0]
            if len(exclude_type) != 0:
                  if word_type not in exclude_type:
                        output.append(node.surface)
            else:
                output.append(node.surface)
        node = node.next

    print("end mecab analysis")
    return output
```

上記のメソッドを使って、寄せ書きのメッセージからワードクラウド作成に必要な単語のリストを出力します。
```python
wordlist = mecab_analysis(text, exclude_type=["助詞", "接続詞"])
```

上の結果には、ひらがな1文字といったものも含まれるため、次の記事を参考にひらがな1文字は除外しました。

https://qiita.com/mocha_xx/items/00c5a968f7069d8e092c

```python
import re

re_hiragana = re.compile(r'^[あ-ん]$')
wordlist = list(filter(lambda s: not re_hiragana.fullmatch(s), wordlist))
```

## ワードクラウド出力
以下がワードクラウドを出力する関数です。
`stop_words`引数に出力に含めない単語を指定することができます。

```python
import matplotlib.pyplot as plt
from wordcloud import WordCloud

def create_wordcloud(text, stop_words = []):
    print("start create wordcloud")

    # 環境に合わせてフォントのパスを指定する。
    fpath = "/usr/share/fonts/truetype/fonts-japanese-mincho.ttf"
    # 引数の詳細は、https://amueller.github.io/word_cloud/generated/wordcloud.WordCloud.html　参照
    wordcloud = WordCloud(background_color="white",font_path=fpath, mask=flower_mask, colormap="spring", \
                          stopwords=set(stop_words), contour_width=1, contour_color='coral').generate(text)
    print("end create wordcloud")

    print("now showing")
    plt.figure(figsize=(15,12))
    plt.imshow(wordcloud)
    plt.axis("off")
    plt.show()
```

上記の関数を用いて、ワードクラウドを出力します。
ストップワードを調整して、良い感じになるようにします。
```python
create_wordcloud(" ".join(wordlist), stop_words = [u'ます', u'です', u'ました', u'こと'])
```

![letter_yosegaki.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1423540/0dd587c7-8fde-651f-9925-64c4235c477f.png)


# ipynbファイル

参考までに、今回利用したipynbファイルを記載します。

<details><summary><b>──────────────────┐<br>│&nbsp;&nbsp;&nbsp;&nbsp;ソースコードを表示（折りたたみ）&nbsp;&nbsp;&nbsp;│<br>└──────────────────┘</b></summary><div>

```python:wordcloud_yosegaki.ipynb
{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "code",
      "metadata": {
        "id": "6oa_YJfWIaX_"
      },
      "source": [
        "!apt update\n",
        "!apt install fonts-ipafont fonts-ipaexfont\n",
        "!pip install mecab-python3 unidic-lite"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "import MeCab\n",
        "print(MeCab.Tagger().parse(\"すもももももももものうち\"))"
      ],
      "metadata": {
        "id": "Teefs-N4XkAT"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Obsh0FhCKnrB"
      },
      "source": [
        "from google.colab import drive\n",
        "from PIL import Image\n",
        "import numpy as np\n",
        "# Googleドライブをマウント\n",
        "drive.mount('/content/drive')\n",
        "\n",
        "text = open('/content/drive/MyDrive/Colab/Yosegaki/messages.txt').read()\n",
        "letter_mask = np.array(Image.open('/content/drive/MyDrive/Colab/Yosegaki/letter_mask.png'))\n",
        "flower_mask = np.array(Image.open('/content/drive/MyDrive/Colab/Yosegaki/flower_mask.png'))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-2DTt-djMUoJ"
      },
      "source": [
        "print(text)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "9hO5znLtMBgD"
      },
      "source": [
        "import MeCab as mc\n",
        "\n",
        "def mecab_analysis(text, exclude_type=[]):\n",
        "    print(\"start mecab analysis\")\n",
        "    t = mc.Tagger()\n",
        "    node = t.parseToNode(text)\n",
        "    output = []\n",
        "    while(node):\n",
        "        if node.surface != \"\":\n",
        "            word_type = node.feature.split(\",\")[0]\n",
        "            if len(exclude_type) != 0:\n",
        "                  if word_type not in exclude_type:\n",
        "                        output.append(node.surface)\n",
        "            else:\n",
        "                output.append(node.surface)\n",
        "        node = node.next\n",
        "\n",
        "    print(\"end mecab analysis\")\n",
        "    return output"
      ],
      "execution_count": 12,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "bL5-B8pKMJTJ"
      },
      "source": [
        "wordlist = mecab_analysis(text, exclude_type=[\"助詞\", \"接続詞\"])\n",
        "print(wordlist)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "import re\n",
        "\n",
        "re_hiragana = re.compile(r'^[あ-ん]$')\n",
        "wordlist = list(filter(lambda s: not re_hiragana.fullmatch(s), wordlist))\n",
        "print(wordlist)"
      ],
      "metadata": {
        "id": "G8ubrS0SiRVu"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "4sklSZeUM4E1"
      },
      "source": [
        "import matplotlib.pyplot as plt\n",
        "from wordcloud import WordCloud\n",
        "\n",
        "def create_wordcloud(text, stop_words = []):\n",
        "    print(\"start create wordcloud\")\n",
        "\n",
        "    # 環境に合わせてフォントのパスを指定する。\n",
        "    fpath = \"/usr/share/fonts/truetype/fonts-japanese-mincho.ttf\"\n",
        "    # 引数の詳細は、https://amueller.github.io/word_cloud/generated/wordcloud.WordCloud.html　参照\n",
        "    wordcloud = WordCloud(background_color=\"white\",font_path=fpath, mask=flower_mask, colormap=\"spring\", \\\n",
        "                          stopwords=set(stop_words), contour_width=1, contour_color='coral').generate(text)\n",
        "    print(\"end create wordcloud\")\n",
        "\n",
        "    print(\"now showing\")\n",
        "    plt.figure(figsize=(15,12))\n",
        "    plt.imshow(wordcloud)\n",
        "    plt.axis(\"off\")\n",
        "    plt.show()"
      ],
      "execution_count": 30,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "create_wordcloud(\" \".join(wordlist), stop_words = [u'ます', u'です', u'ました', u'こと'])"
      ],
      "metadata": {
        "id": "T-S2owPdb5DD"
      },
      "execution_count": null,
      "outputs": []
    }
  ]
}
```
</div></details>

# 参考
https://qiita.com/kiyokiyo_kzsby/items/554c571404f3f9844aaf

https://github.com/SamuraiT/mecab-python3

https://github.com/amueller/word_cloud

https://tecsingularity.com/wordcloud/wc_mask/