5日と20日は歌詞と遊ぼう。

歌詞を読み、統計したりしています。

パジャマパーティーズ『パジャマパーティーズのうた』の状態遷移図を描いた!

こんにちは!

パジャマパーティーズパジャマパーティーズのうた』、めちゃいい歌じゃないですか??

作詞だれなの!?って思って調べたらナガノさんでした。信頼✨

ところでこの曲の歌詞はこういう感じ。

ウ ウ・ワ・ワ・ウワ
ウワワタ~クタ~クチャァオ
ウワワワウワ ワーイワイトレリ
ウワワワイワイ タラチロ

「ウ」のあとは最初以外は全部「ワ」になりますが、「ワ」のあとは記号とかを除くと

  • 「ウ」に戻る
  • 「ワ」のまま
  • 「タ」という新顔1
  • 「イ」という新顔2

の4パターンがあります。「ウ」と「ワ」が多くて、「タ」「イ」が少ないかな。

これを可視化する方法として状態遷移図というものがあります! 今回はそれを描いてみましょう✏️

状態遷移図を描くぞ!

可視化するとこう!

パジャマパーティーズパジャマパーティーズのうた』の状態遷移図

きょうはそのためのコーディングをお見せしましょう✨

まあお見せしましょうっつってもわたしの能力なんてタカが知れています。

お願いChatGPT!この歌詞で状態遷移図を描いて〜〜!

もちろんです!!

import numpy as np
import matplotlib.pyplot as plt
import re
import networkx as nx

長いから略! 全文あとで書くから下の方見てね!

ライブラリーはいつめんだけど、 networkx はわたし初めて使いました。

今回は状態遷移図の可視化のために使うんですけど、グラフ理論の計算にも使えるそうです。探索とかできるらしいです、すご。

# テキストデータ
text = '''(ここに歌詞)'''

# 文字列の整形とモーラへの分割
text = re.sub(r'[\n ・~~ー♪()]','' , text)
text = re.findall(r'[^ァィゥェォッャュョ][ァィゥェォッャュョ]*', text)

unique_chars = sorted(set(text))
char_to_index = {char: i for i, char in enumerate(unique_chars)}
index_to_char = {i: char for char, i in char_to_index.items()}

まずは歌詞の文字列の整形をおこないます。

1文字単位で状態遷移図を書く場合はそのままでもいいんですが、今回は記号を取り除き、またモーラに分割できるようにしました。たとえば「シ」「ャ」に分けずに「シャ」でまとめる、みたいな感じです。

今回は「ー」も記号と見なして消しちゃったので、正確にいうとモーラではないなこれ。いったん「文字」って呼ぼ。

# 遷移行列の初期化
transition_matrix = np.zeros((len(unique_chars), len(unique_chars)))

# 遷移頻度の計算
for i in range(len(text) - 1):
    current_index = char_to_index[text[i]]
    next_index = char_to_index[text[i + 1]]
    transition_matrix[current_index, next_index] += 1

次にどの文字からどの文字への遷移が何回あるか、総当たり表を作ります。

最初の2行分だけ抜き出して作るとこういう感じ。

チャァ
1 0 0 0 0 3
0 0 0 0 0 0
0 0 0 1 1 0
0 0 2 0 0 0
チャァ 0 1 0 0 0 0
2 0 0 1 0 2

これのでかい版を作るんだね〜。

# ノードごとの頻度の計算
node_frequencies = np.sum(transition_matrix, axis=1)

# ノードサイズの計算
node_sizes = node_frequencies * 20 + 2

# 遷移確率の計算
row_sums = transition_matrix.sum(axis=1)
# np.divideすると遷移確率が出せる、しないと粗頻度
# transition_matrix = np.divide(transition_matrix, row_sums[:, np.newaxis], where=row_sums[:, np.newaxis] != 0)

ひとつひとつの文字はノードといい、文字と文字を繋ぐ線はエッジと言います。

今回はいっぱい出てくる文字(=ノード)をでかくしたいので、ノードのサイズ用の配列を作っておいてあとで使います。

エッジについても、人気の線は太くしたいので算出しておきます。

最初は確率で出そうと思った(「ウ」のあとは「ワ」に行く確率が高いので線を太くする)んですけど、頻度で出す(「ウ」のあとに「ワ」に行く回数が多ければ太くする)ほうが映えるのでそうします。コメントアウトして両方残してあります。

# 状態遷移図の作成
G = nx.DiGraph()
for i, current_char in index_to_char.items():
    for j, next_char in index_to_char.items():
        if transition_matrix[i, j] > 0:
            G.add_edge(current_char, next_char, weight=transition_matrix[i, j])

# エッジの太さの計算
edge_widths = [G[u][v]['weight'] * .2 + .6 for u, v in G.edges()]

# カスタムカラーマップ用
colors = range(len(G.edges))

さっきまではグラフのデータを生成していましたが、今度は状態遷移図を作成することにします。

この二重ループはたぶん内包表記なら1行で書けるんだけど、別に重いデータじゃないしループ描いた方が見やすいな。

# 描画
pos = nx.spring_layout(G, k=2.4)

edge_labels = {(u, v): f'{d['weight']:.2f}' for u, v, d in G.edges(data=True)}
plt.figure(figsize=(10, 8))
edges = nx.draw_networkx_edges(G, pos, alpha=0.5, width=edge_widths, edge_color=colors, edge_cmap=plt.cm.winter)
nodes = nx.draw_networkx_nodes(G, pos, alpha=0.5, node_size=[node_sizes[char_to_index[node]] for node in G.nodes()], node_color='dodgerblue', cmap='terrain')
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic', font_size=16, font_weight='bold')

plt.title('『パジャマパーティーズのうた』状態遷移図')
plt.show()

最後に描画。Matplotlibを使って描きます。

透明度とかを別々に設定するためにnodesとedgesを分けたものの、結局同じになりました。

IPAexGothicって指定すれば日本語が使えてありがたい✨

こうやってみるとカンタンですね! 時間? 12時間ぐらいかかったよ!!

改めて画像を見てみましょう。

パジャマパーティーズパジャマパーティーズのうた』の状態遷移図

「ウ」と「ワ」の間には一番太い線があります。飛行機でいうと「ウ」が新千歳空港で「ワ」が羽田空港だな。

「ワ」の上には丸がついています。「ワ」のあとにもう一度「ワ」が来がちということもわかります。

また「タ」と「ク」の間にもかなり太い線があります。「タ」っていったら「ク」って言いがちなんですね。

左側には「ツ」がありますが、そのあとは必ず「ナ」、さらにそのあとは必ず「サ」になります。「ツナサラミ」への確実な線が感じられます。

こういうのあいうえおとかでやったら踏みがちな韻とかがわかりそうだね〜〜。

おわり。

パジャマパーティーズのうた

パジャマパーティーズのうた

全文↓

import numpy as np
import matplotlib.pyplot as plt
import re
import networkx as nx

# テキストデータ
text = '''(ここに歌詞)'''

# 文字列の整形とモーラへの分割
text = re.sub(r'[\n ・~~ー♪()]','' , text)
text = re.findall(r'[^ァィゥェォッャュョ][ァィゥェォッャュョ]*', text)

unique_chars = sorted(set(text))
char_to_index = {char: i for i, char in enumerate(unique_chars)}
index_to_char = {i: char for char, i in char_to_index.items()}

# 遷移行列の初期化
transition_matrix = np.zeros((len(unique_chars), len(unique_chars)))

# 遷移頻度の計算
for i in range(len(text) - 1):
    current_index = char_to_index[text[i]]
    next_index = char_to_index[text[i + 1]]
    transition_matrix[current_index, next_index] += 1

# ノードごとの頻度の計算
node_frequencies = np.sum(transition_matrix, axis=1)

# ノードサイズの計算
node_sizes = node_frequencies * 20 + 2

# 遷移確率の計算
row_sums = transition_matrix.sum(axis=1)
# np.divideすると遷移確率が出せる、しないと粗頻度
# transition_matrix = np.divide(transition_matrix, row_sums[:, np.newaxis], where=row_sums[:, np.newaxis] != 0)

# 状態遷移図の作成
G = nx.DiGraph()
for i, current_char in index_to_char.items():
    for j, next_char in index_to_char.items():
        if transition_matrix[i, j] > 0:
            G.add_edge(current_char, next_char, weight=transition_matrix[i, j])

# エッジの太さの計算
edge_widths = [G[u][v]['weight'] * .2 + .6 for u, v in G.edges()]

# カスタムカラーマップ用
colors = range(len(G.edges))

# 描画
pos = nx.spring_layout(G, k=2.4)

edge_labels = {(u, v): f'{d['weight']:.2f}' for u, v, d in G.edges(data=True)}
plt.figure(figsize=(10, 8))
edges = nx.draw_networkx_edges(G, pos, alpha=0.5, width=edge_widths, edge_color=colors, edge_cmap=plt.cm.winter)
nodes = nx.draw_networkx_nodes(G, pos, alpha=0.5, node_size=[node_sizes[char_to_index[node]] for node in G.nodes()], node_color='dodgerblue', cmap='terrain')
nx.draw_networkx_labels(G, pos, font_family='IPAexGothic', font_size=16, font_weight='bold')

plt.title('『パジャマパーティーズのうた』状態遷移図')
plt.show()