はじめに
つい先日、ビートまりお氏によって「Help me, ERINNNNNN!!」のアニメーション付き動画が公開されました。
この曲ではファンが一斉に、( ゚∀゚)o彡゜えーりん!えーりん!とコールする文化があります。詳しくはニコニコ大百科を参照してください。
今回の新たな動画はプレミア公開されたため、多くのファンがチャットで「えーりん!」と叫んでいます。
動画を見れば、えーりんに関するコメントがどれだけ多いか直感的に分かりますが、今回は具体的な数値として「えーりん」チャットの件数や割合をPythonを使って分析してみたいと思います。
今回作成したコードは以下のリポジトリにあります。
データの取得
まず、YouTubeのコメントを集めます。最初はYouTube Data APIからコメントを取得しようとしていましたが、チャットの部分を取得する方法がよくわからなかったため、今回はChat Downloaderというツールを使用しました。
公式ページを参考にして以下のコマンドでパッケージをダウンロードし、コメントをダウンロードします。
pip install chat-downloader
chat_downloader https://www.youtube.com/watch?v=X8z23t428kU --output chat.json
実際に保存されたデータを見てみましょう。
[
...
{
"action_type": "add_chat_item",
"author": {
"id": "UCxxxxxxxxxxxxxxxxxxxxxxx",
"images": [
...
],
"name": "username_of_sender"
},
"message": "\u3048\u30fc\u308a\u3093( \uff9f\u2200\uff9f)o\u5f61\u00b0",
"message_id": "xxxxxxxxxx",
"message_type": "text_message",
"time_in_seconds": -10,
"time_text": "-0:10",
"timestamp": 1714900197822561
},
...
データが正しく取得されていることが確認できます。このデータを元に解析を行います。
データの前処理
前項にてチャットデータがダウンロードできましたが、autherのnameとmessage がUnicodeエスケープされた文字列として保存されてしまっています。
また、メッセージも「えーりん」が含まれたものであるか分別する必要があるため、前処理をしたいと思います。
データの前処理を行うPythonコード
import json
import re
def replace_hyphen(text, replace_hyphen):
"""全ての横棒をーに置換する
Args:
text (str): 入力するテキスト
replace_hyphen (str): 置換したい文字列
Returns:
(str): 置換後のテキスト
"""
hyphens = "-˗ᅳ᭸‐‑‒–—―⁃⁻−▬─━➖ーㅡ﹘﹣-ー𐄐𐆑 "
hyphens = "|".join(hyphens)
return re.sub(hyphens, replace_hyphen, text)
# ラベル付けするためのキーワードパターン
erin = ["えーりん", "エーリン", "EIRIN", "eirin", "Eirin", "ERIN", "erin", "Erin"]
rine = ["りんえ", "リンエ", "RINE", "rine", "Rine"]
opi = ["おっぱい", "オッパイ", "OPPAI", "oppai", "Oppai", "おっπ"]
inaba = ["因幡", "いなば", "イナバ", "INABA", "inaba", "Inaba", "うどん"]
touho = ["東方", "とうほう", "TOUHOU", "Touhou", "touhou", "TOUHO", "touho", "Touho"]
reimu = ["霊夢", "れいむ", "REIMU", "reimu", "Reimu"]
marisa = ["魔理沙", "まりさ", "MARISA", "marisa", "Marisa"]
# 正規表現パターンのリスト
patterns = [
{"pattern": re.compile("|".join(erin)), "label": "erin"},
{"pattern": re.compile("|".join(rine)), "label": "rine"},
{"pattern": re.compile("|".join(opi)), "label": "opi"},
{"pattern": re.compile("|".join(inaba)), "label": "inaba"},
{"pattern": re.compile("|".join(touho)), "label": "touho"},
{"pattern": re.compile("|".join(reimu)), "label": "reimu"},
{"pattern": re.compile("|".join(marisa)), "label": "marisa"},
]
# JSONファイルの読み込み
with open("chat.json", "r", encoding="utf-8") as f:
# data = json.loads(f.read().encode("utf-8"))
data = json.load(f)
print(f"Loaded {len(data)} messages")
# データの前処理とラベル付け
for i in range(len(data)):
data[i]["message"] = replace_hyphen(data[i]["message"], "ー")
for pattern in patterns:
if pattern["pattern"].search(data[i]["message"]):
data[i]["label"] = pattern["label"]
break
else:
data[i]["label"] = "other"
# 結果を新しいJSONファイルに保存
encoded_data = json.dumps(data, ensure_ascii=False)
with open("encoded_chat.json", "w", encoding="utf-8") as f:
f.write(encoded_data)
簡単なコードの解説
- 正規表現で対象となる文字列が含まれているか判別し、"label"という新たなプロパティをデータに追加している
- json.dumpsで ensure_ascii=False を追加することで文字列のエスケープ処理をする
- 伸ばし棒の文字コード違いが一部あったのでその正規化処理
今回作成したコードでは単純な文字一致でラベル付けしているため、ラベルが付与されなかったものは全て"other"となっているため、お好みで適切なラベルをエディタ等で検索しながら修正してください。
対象の文字列が含まれているかだけの単純な形ではありますが、これでデータへのラベル付けが完了しました。
分析と結果
チャットを実際に解析してみたいと思います。
今回は
- 各ラベルの件数
- 全体に占めるえーりんが含まれるコメントとそれ以降のコメントの割合
- えーりん以外のコメントに占める、それぞれのラベルの割合
- 時間ごとのコメントの変化
をみたいと思います。
データの前処理を行うPythonコード
import json
import pandas as pd
import matplotlib.pyplot as plt
json_file = "encoded_chat.json"
labels = ["erin", "rine", "opi", "inaba", "touho", "reimu", "marisa"]
def load_json(json_file):
with open(json_file, "r", encoding="utf-8") as f:
return json.load(f)
def json_to_dataframe(data):
records = [
{
"message": item["message"],
"time_in_seconds": item["time_in_seconds"],
"label": item["label"],
}
for item in data
]
return pd.DataFrame.from_records(records)
def dataframe_to_time_seconds(df):
df["time_in_seconds"] = df["time_in_seconds"].apply(lambda x: int(x))
return df
def plot_label_distribution(
df, selected_labels=labels, flag=False, filename="label_distribution.png"
):
label_counts = df["label"].value_counts()
if flag:
other = label_counts[~label_counts.index.isin(selected_labels)].sum()
new_label_counts = label_counts[label_counts.index.isin(selected_labels)]
new_label_counts["other"] = other
label_counts = pd.Series(new_label_counts, index=selected_labels + ["other"])
else:
label_counts = pd.Series(label_counts, index=selected_labels)
plt.pie(
label_counts,
labels=label_counts.index,
autopct="%1.1f%%",
startangle=90,
counterclock=False,
)
plt.savefig(filename)
plt.show()
def plot_label_over_time(df, selected_labels, filename="label_count_over_time.png"):
df = dataframe_to_time_seconds(df)
plt.figure(figsize=(10, 6))
for label in selected_labels:
label_data = df[df["label"] == label]
if not label_data.empty:
label_counts_over_time = label_data.groupby("time_in_seconds").size()
label_counts_over_time.plot(label=label)
plt.title("Label Count Over Time")
plt.xlabel("Time (seconds)")
plt.ylabel("Count")
plt.legend(title="Label")
plt.savefig(filename)
plt.show()
def main():
data = load_json(json_file)
df = json_to_dataframe(data)
print("データ数:", len(df))
print(" えーりんを含むメッセージ数:", len(df[df["label"] == "erin"]))
print(" えーりん以外を含むメッセージ数:", len(df[df["label"] != "erin"]))
print("各ラベルのメッセージ数")
for label in labels + ["other"]:
print(f" {label}を含むメッセージ数:", len(df[df["label"] == label]))
plot_label_distribution(df, selected_labels=["erin"], flag=True, filename="label_distribution_erin.png")
plot_label_distribution(df, selected_labels=labels[1:] + ["other"], flag=False, filename="label_distribution_others.png")
plot_label_over_time(df, labels + ["other"])
if __name__ == "__main__":
main()
出力された結果は以下の通りです。
結果より
結果より、コメントにおける圧倒的な「えーりん」率が確認できました。
動画の長さは5:10であるため、平均秒間えーりん数は9.5225コメント(えーりん)/秒となり非常に高い水準です(動画開始前の10秒間を含めた場合でも 9.225コメント(えーりん)/秒)。
次いでラベル付けされているコメントの中で多かったのが「おっぱい」に関するもので、皆おふざけが好きなようです。
その他のコメントとしては「きたー」「かわいい」「最高」「88888」や投稿主を立てるコメントが多々ありました。
さいごに
この分析を通じて、本家の動画公開から20年が経過してもなお、このような盛り上がりと文化が継続している凄さと、しっかりと訓練されていることがよく分かりました。
インターネットって面白いですね。
最後まで読んでいただきありがとうございました。この記事が面白いと思ったら、ぜひ「いいね」やリアクションをいただけると嬉しいです。