[Python+MeCab+WordCloud] 沼津のご当地歌手の歌詞から沼津愛を図示した


はじめに

突然ですが、皆様は飯田徳孝氏をご存知でしょうか。
あらびき団という番組を視聴していた方や、とあるサッカーチームを応援している方ならご存知かもしれません。

端的に説明すると、普段は漁業関係の組合の職員として勤務する傍ら、「静岡県沼津市ご当地シンガーソングライター」として、沼津市をアピールする楽曲を歌い、聴く人を皆笑顔にしている方です。

そんな飯田徳孝氏の歌詞を分析してみることで、彼の沼津愛を具体的に図示することが出来ないだろうか、と考えました。
そこで、今回は「形態素解析」を行い、「WordCloud」を用いることで、登場頻度の高いワードをビジュアライズしてみました。

アウトプットのため、ライブラリや引数の説明も盛り込むこととします。

0. 下準備 : MeCabと辞書を設定する

まず、MeCabという形態素分解(文章を単語単位で分解すること)のエンジンをインストールし、新語や固有名詞を扱いやすくするための追加の辞書を導入する必要があります。
インストールに関しては、以下の記事等を参考にしてください。

Windowsの場合は結構めんどくさいらしいですが、以下の記事が参考になると思います。

また、Google colaboratory上ならば、環境に依存せずにコピペで使える状態にすることができるので、それでもいいと思います。

今回は辞書のpathを環境変数に設定し、Python上で受け取っています。まず、きちんと動作しているかを確かめます。

python.ipynb
import MeCab
import os

# 事前に設定した環境変数(NEologdのパス)をos.getenvで読み取る
path = os.getenv("MECAB_NEO")  
path = "-d " + path
# インスタンスを生成
tagger = MeCab.Tagger(path)
print(tagger.parse("AtCoderとGitHubとQiita"))

"""
出力:
AtCoder	名詞,固有名詞,組織,*,*,*,AtCoder,アットコーダー,アットコーダー
と	助詞,並立助詞,*,*,*,*,と,ト,ト
GitHub	名詞,固有名詞,一般,*,*,*,GitHub,ギットハブ,ギットハブ
と	助詞,並立助詞,*,*,*,*,と,ト,ト
Qiita	名詞,固有名詞,一般,*,*,*,Qiita,キータ,キータ
EOS
"""

MeCab.Tagger("-d "+path)が何をしているかというと、「pathの先にある辞書が入った解析ツールを呼び出してください」と指示しているようなものと捉えてください。-dは辞書を指定する引数です。
また、parseは「解析結果を1行ずつの文字列として取得してください」とtaggerに指示するメソッドです。出力は文字列なので、それぞれの要素を取り出して使いたい場合はstr.split(",")などを用いる必要があります。

NEologdが適用されているかどうかは、国語辞典に載っていないような単語や最近の有名人などを入れてみて、適切に分解されていれば大丈夫です。

1. とりあえずミスチルの歌詞を分析してみる

とりあえず、たくさんの方に分かりやすいよう、Mr.Childrenの「名もなき詩」の歌詞を解析してみることにします。

なお、私は歌詞を全て手打ちしましたが、サーバーの負荷やサイトの規約を十分に注意した上で、歌詞のサイトからスクレイピングするという方法もあります。ただ、取得する間隔を十分に開けるなど、慎重に行うようにしてください。

歌詞のスクレイピングを行なっている記事

また、歌詞は著作物です。取り扱いには注意してください。

txt = "NamonakiUta.txt" # 名もなき詩の歌詞のtxtファイル
with open(txt, mode = "r", encoding="utf-8") as f:
    lyric = f.read()

まず、歌詞のテキストを文字列として取得します。txtファイルから文字列を読み取る場合、上記のようにファイルを開いてread()で読み取ることができます。 with open()を使えばclose()を書かずに済みます。

また、xlsxなどから読み取ったりすると、改行コード\nなどが付いてくる場合があるので、その場合はstr.rstrip()などで取り除きます。

ここからは具体的にプログラムを書いていきます。まず、必要なライブラリをimportします。

from matplotlib import pyplot as plt # 図表を表示するライブラリ
from wordcloud import WordCloud # WordCloudのライブラリ
import numpy as np # 画像を情報として扱うためのnumpy
from PIL import Image # 画像を読み取るライブラリ

さて、先ほど作った解析ツールtaggerを用いて単語を分解して取得したいのですが、今度はparseToNodeというメソッドを使います。
Nodeとは双方向リストとして表現できるようにしたものですが、「分解した単語をループで1つずつ処理しやすくする」ためのメソッドだと思ってください。

# 形態素分解してnodeとして取得する
node = tagger.parseToNode(lyric)

取得したnodeを1つずつ処理していきます。
まず、node.surfaceには単語が、node.featureには特徴が入っています。最初の例だと、surfaceには「AtCoder」が、featureには「名詞,固有名詞,組織,...」です。
先に述べた通り、featureに入っているのは文字列なので、コンマごとに分解して0番目の要素を取り出すことで、品詞だけを取り出しています。
そして「名詞か形容詞か動詞」という条件に一致する単語をword_listに入れる作業を行なっています。

また、node = node.nextをループの最後に書くことで、次の単語を呼び出しています。

# 条件に合う単語を格納するリスト
word_list = []

while node:
    # node.featureに品詞の情報が含まれているので抽出
    word_type = node.feature.split(",")[0]
    if (word_type == "名詞" or word_type == "形容詞" or word_type == "動詞"):
        word_list.append(node.surface) # node.surface -> 分解した単語
    node = node.next

#WordCloudに読ませるために、半角スペースごとの文字列として繋ぎ合わせる
words = " ".join(word_list)

さて、あとはWordCloudに文字列を読ませるだけなのですが、WordCloudのユニークな機能を先に紹介しておきます。
普通にWordCloudを作成すると長方形の形で作られるのですが、事前に画像を設定しておくことで、その画像の型に沿ったWordCloudを作成することができます。

こんな感じで、マスクしたい形の画像を用意してください。白黒に過去する必要はありせんが、背景にしたい部分は白く塗りつぶしておく必要があります。手作業で塗りつぶす場合でも、ある程度の面積がないと文字は乗らないので、ちょっとぐらい汚れてても大丈夫だと思います。
また、透過だと上手くいかないことがあるので、その場合は不透過にして実行し直してください。

# maskする画像を指定できる
mask_array = np.array(Image.open("maskしたい形の画像のpath"))

Image.open()で読み込んだ画像をnp.arrayに渡すことで、画素ごとの座標と色を記録した配列として処理することができます。

# ワードクラウド作成
W = WordCloud(width = 1920, height = 1080, 
              background_color = 'white', 
              # 日本語の場合、日本語に対応したフォントのpathを指定
              font_path = '/Library/Fonts/ヒラギノ角ゴシック W4.ttc',
              max_words = 500, 
              regexp = r"[\w']+",  # 1文字の単語を表示させるため
              stopwords = ["ん", "さ"], # 特定の単語を含まないようにできる
              colormap = "rainbow",
              mask = mask_array,
              collocations = False
             ).generate(words)

W.to_file('namonakiUta_wordCloud.png') # 名前をつけて保存
plt.imshow(W) # 画像をグラフとして読み込む
plt.axis('off') # グラフの軸を非表示
plt.show()

文字列やマスクする画像などを引数として渡すことで、ようやくWordCloudの画像を作ることができました。
必須の部分は.generate(words)の部分だけですが、colormapはお好みで変更できますし、font_pathは実行環境によって異なるので、適宜変更する必要があるでしょう。ちなみに、デフォルトのフォントだと日本語が豆腐に化けるので、邦楽を取り扱うなら必ず指定する必要があります。
また、stopwordsに文字列の配列を設定することで、その文字列を表示しないようにできます。まずは何も指定せずに画像を表示してみて、邪魔な単語があったら後で追加するのが良いと思います。

完成したものがこちらです。頻出する言葉が大きく表示されるのがWordCloudの特徴です。

2. 飯田徳孝氏の歌詞を全てぶち込む

長々と書いてきましたが、いよいよ本題です。
ただ、手順自体は全く同じなので、サラッと記述します。

txt = "noritaka_lyrics.txt"
with open(txt, mode = "r", encoding = "utf-8") as f:
    lyrics = f.read()

node = tagger.parseToNode(lyrics)

word_list = []

while node:
    word_type = node.feature.split(',')[0]
    if word_type == "名詞" or word_type == "形容詞" or word_type == "動詞":
        word_list.append(node.surface)
    node = node.next

words = " ".join(word_list)

mask_array = np.array(Image.open("Iida_noritaka.jpg"))

W = WordCloud(width=1920, height=1080, 
              background_color = 'white', 
              font_path = '/Library/Fonts/ヒラギノ角ゴシック W4.ttc', 
              max_words = 500, 
              regexp = r"[\w']+",
              stopwords = ["い", "の", "ん", "A", "La"],
              colormap = "rainbow",
              collocations = False,
              mask = mask_array
             ).generate(words)

W.to_file('Iida_noritaka_WordCloud.png')
plt.imshow(W)
plt.axis('off')
plt.show()

「沼津」、「NUMAZU」、「Love」、「びゅうお」。沼津愛の大きさが、やはり歌詞にも表れていることがわかりました。
「旬」が大きいのは「旬!旬彩街」という曲で連呼しているためでしょうか。
また、「あなた」「You」「あなたと」 という呼びかけが多いことも分かります。

「聴いてくれるあなたに、 沼津の魅力を精一杯伝えたい」

という飯田徳孝氏の姿勢が顕著に表れています。多分。

最後に

今回は歌詞を分析してみましたが、様々なアーティストの歌詞を分析してクイズにするのも面白そうですし、引っ張ってきたツイートを解析してプレゼンテーションなどに差し込むのもビジュアルとして分かりやすいと思うので、皆さんもぜひ活用してみてください。