この記事では、Google Playレビュー本文を対象に、ポジティブ・ネガティブの傾向をスコア化し、月次推移として可視化する方法を解説します。
レビュー件数や平均スコアだけでは、「ユーザーの反応が好意的なのか、不満寄りなのか」までは分かりにくい場合があります。レビュー本文に含まれる感情語を集計することで、アップデートやイベント後の反応変化を補助的に確認できます。
本記事では、取得済みのGoogle PlayレビューCSVを使い、日本語レビューの前処理、簡易的な感情辞書によるスコアリング、月次集計、Plotlyによる可視化までをPythonで実装します。
この記事でできること
- Google Playレビュー本文を使って感情スコアを計算する
- 日本語レビューを形態素解析して感情語を抽出する
- ポジティブ率・ネガティブ率を月次で集計する
- 平均感情スコアの推移をPlotlyで可視化する
- 感情辞書を分析対象に合わせて調整する
- 感情分析結果を解釈するときの注意点を理解する
想定読者
- Google Playレビューのポジティブ・ネガティブ傾向を見たい方
- レビュー本文をスコア化して時系列で確認したい方
- アップデート後に不満が増えたかをざっくり確認したい方
- Pythonで簡易的な日本語感情分析を試したい方
- レビュー分析記事やアプリ改善点の整理に使う材料を作りたい方
事前準備
この記事では、Google Playレビューを取得済みで、CSVファイルとして保存されている前提で進めます。レビュー取得がまだの場合は、先に以下の記事を確認してください。
Google Playのデータ取得方法まとめ|google-play-scraperでアプリ情報・レビューを取得する
また、日本語レビューの前処理やストップワード調整については、以下の記事でも詳しく扱っています。
Google Playレビューのテキスト分析入門|ワードクラウド・共起ネットワーク・TF-IDFをPythonで可視化
今回使う主な列は以下です。
| 列名 | 内容 |
|---|---|
content | レビュー本文 |
score | 星評価 |
at | レビュー投稿日 |
reviewCreatedVersion | レビュー投稿時のアプリバージョン |
本記事では、例として reviews_genshin_paged.csv というCSVファイルを読み込みます。ファイル名は手元のデータに合わせて変更してください。
感情分析で見ること
ここで行う感情分析は、レビュー本文に含まれる単語をもとに、ポジティブ寄り・ネガティブ寄りの傾向を数値化するものです。
| 指標 | 意味 | 見方 |
|---|---|---|
| 感情スコア | レビュー本文内のポジ・ネガ語から計算した値 | プラスならポジティブ寄り、マイナスならネガティブ寄り |
| ポジティブ率 | 感情スコアが0より大きいレビューの割合 | 好意的な表現を含むレビューの比率 |
| ネガティブ率 | 感情スコアが0より小さいレビューの割合 | 不満表現を含むレビューの比率 |
| 平均感情スコア | 月ごとの感情スコア平均 | 月次の反応がポジ・ネガどちらに寄っているかを見る |
ただし、辞書ベースの感情分析は文脈理解が得意ではありません。たとえば「重いけど面白い」「課金しなくても楽しい」のような文では、単語だけでは正確に判断できない場合があります。
使用ライブラリ
日本語レビューの形態素解析には Janome、可視化には Plotly を使います。
pip install -U pandas janome plotlyCSVを読み込んで日付を整える
まず、取得済みのレビューCSVを読み込みます。レビュー本文、星評価、投稿日がない行は除外します。
import pandas as pd
csv_path = "reviews_genshin_paged.csv"
df = pd.read_csv(csv_path)
df = df.dropna(subset=["content", "score", "at"]).copy()
df["score"] = df["score"].astype(int)
df["at"] = pd.to_datetime(df["at"], errors="coerce")
df = df.dropna(subset=["at"]).copy()
df["month"] = df["at"].dt.to_period("M").astype(str)
print(df[["content", "score", "at", "month"]].head())
print("rows:", len(df))month は月次集計に使います。日次で細かく見たい場合は、df["date"] = df["at"].dt.date のように日付列を作成してもよいです。
簡易的な感情辞書を用意する
次に、ポジティブ語・ネガティブ語の辞書を作成します。ここでは分かりやすさを優先し、簡易的な辞書をコード内に直接定義します。
sentiment_dict = {
# ポジティブ語
"楽しい": 1.0,
"面白い": 1.0,
"最高": 1.0,
"良い": 1.0,
"快適": 1.0,
"嬉しい": 1.0,
"好き": 1.0,
"魅力": 0.8,
"綺麗": 0.8,
"便利": 0.8,
# ネガティブ語
"つまらない": -1.0,
"最悪": -1.0,
"ひどい": -1.0,
"重い": -1.0,
"バグ": -1.0,
"遅い": -1.0,
"落ちる": -1.0,
"残念": -1.0,
"不具合": -1.0,
"イライラ": -1.0,
}辞書は分析対象によって調整が必要です。ゲームレビューでは「ガチャ」「課金」「イベント」「容量」「ストーリー」など、文脈によってポジティブにもネガティブにもなり得る語があります。
日本語レビューを形態素解析する
レビュー本文を単語に分割します。URLや記号を除去し、名詞・形容詞・動詞を中心に抽出します。
import re
import unicodedata
from janome.tokenizer import Tokenizer
tokenizer = Tokenizer(wakati=False)
def normalize_text(text: str) -> str:
text = unicodedata.normalize("NFKC", str(text))
text = re.sub(r"http\S+|www\.\S+", " ", text)
text = re.sub(r"[^\wぁ-んァ-ン一-龥ー]", " ", text)
return text
def tokenize_ja(text: str) -> list[str]:
text = normalize_text(text)
tokens = []
for token in tokenizer.tokenize(text):
base = token.base_form if token.base_form != "*" else token.surface
pos = token.part_of_speech.split(",")[0]
if pos in {"名詞", "形容詞", "動詞"} and base and len(base) > 1:
tokens.append(base)
return tokens
df["tokens"] = df["content"].map(tokenize_ja)
print(df[["content", "tokens"]].head())前処理を強化したい場合は、表記ゆれ補正やストップワード除去を追加します。前処理を厳密にするほど、感情辞書とのマッチング精度も変わります。
レビューごとの感情スコアを計算する
感情辞書に含まれる単語をレビュー内で探し、ポジティブ語・ネガティブ語の平均値を感情スコアとして計算します。
def calc_sentiment_score(tokens: list[str]) -> float:
scores = [
sentiment_dict[token]
for token in tokens
if token in sentiment_dict
]
if not scores:
return 0.0
return sum(scores) / len(scores)
df["sentiment"] = df["tokens"].map(calc_sentiment_score)
df["sentiment_label"] = "neutral"
df.loc[df["sentiment"] > 0, "sentiment_label"] = "positive"
df.loc[df["sentiment"] < 0, "sentiment_label"] = "negative"
print(df[["content", "score", "sentiment", "sentiment_label"]].head())感情辞書に該当する語が含まれないレビューは、ここでは 0.0 として扱っています。これは「中立」とは限らず、「辞書では判定できなかったレビュー」と考える方が自然です。
月次でポジティブ率・ネガティブ率を集計する
レビューごとの感情スコアを月次で集計します。ここでは、ポジティブ率、ネガティブ率、平均感情スコア、レビュー件数をまとめます。
df["pos_flag"] = df["sentiment"] > 0
df["neg_flag"] = df["sentiment"] < 0
monthly_sentiment = (
df.groupby("month")
.agg(
review_count=("sentiment", "size"),
pos_rate=("pos_flag", "mean"),
neg_rate=("neg_flag", "mean"),
avg_sentiment=("sentiment", "mean"),
avg_score=("score", "mean"),
)
.reset_index()
)
print(monthly_sentiment.head())pos_rate と neg_rate は0〜1の比率です。必要に応じて100倍すればパーセント表示にできます。
Plotlyで月次推移を可視化する
月次集計結果をPlotlyで可視化します。ポジティブ率、ネガティブ率、平均感情スコアを同じグラフで確認します。
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(
x=monthly_sentiment["month"],
y=monthly_sentiment["pos_rate"],
name="ポジティブ率",
mode="lines+markers",
))
fig.add_trace(go.Scatter(
x=monthly_sentiment["month"],
y=monthly_sentiment["neg_rate"],
name="ネガティブ率",
mode="lines+markers",
))
fig.add_trace(go.Scatter(
x=monthly_sentiment["month"],
y=monthly_sentiment["avg_sentiment"],
name="平均感情スコア",
mode="lines+markers",
line=dict(dash="dot"),
))
fig.update_layout(
title="Google Playレビュー感情スコア(月次推移)",
xaxis_title="年月",
yaxis_title="スコア / 比率",
template="plotly_white",
height=700,
)
fig.show()
fig.write_html(
"sentiment_monthly_trend.html",
include_plotlyjs="cdn",
full_html=True,
)グラフを見ると、特定の月にポジティブ率が上がったのか、ネガティブ率が上がったのかを確認できます。アップデートやイベントの時期と照らし合わせると、ユーザー反応の変化を読み取りやすくなります。
レビュー件数と平均スコアも一緒に見る
感情スコアだけを見ると、レビュー件数の少ない月で大きくブレて見える場合があります。そのため、レビュー件数や平均星評価も合わせて確認するのがおすすめです。
fig_score = go.Figure()
fig_score.add_trace(go.Bar(
x=monthly_sentiment["month"],
y=monthly_sentiment["review_count"],
name="レビュー件数",
yaxis="y1",
))
fig_score.add_trace(go.Scatter(
x=monthly_sentiment["month"],
y=monthly_sentiment["avg_score"],
name="平均星評価",
mode="lines+markers",
yaxis="y2",
))
fig_score.update_layout(
title="レビュー件数と平均星評価(月次)",
xaxis_title="年月",
yaxis=dict(
title="レビュー件数",
side="left",
),
yaxis2=dict(
title="平均星評価",
overlaying="y",
side="right",
range=[1, 5],
),
template="plotly_white",
height=700,
)
fig_score.show()感情スコアの変化と星評価の変化が一致している場合もあれば、一致しない場合もあります。星評価は低いが本文には好意的な表現が含まれる、または星評価は高いが不満点も書かれている、といったケースもあるためです。
感情語の頻度を確認する
感情辞書が対象アプリに合っているか確認するため、実際にマッチした感情語の頻度を見てみます。
from collections import Counter
import itertools
matched_tokens = [
token
for tokens in df["tokens"]
for token in tokens
if token in sentiment_dict
]
sentiment_word_counts = Counter(matched_tokens)
sentiment_word_table = pd.DataFrame(
sentiment_word_counts.most_common(30),
columns=["word", "freq"],
)
print(sentiment_word_table)ここで出てくる単語を確認し、分析対象アプリに合わない語や、文脈によって意味が変わる語がないかを見直します。
感情辞書を調整する
感情辞書は、分析対象のジャンルやアプリによって調整が必要です。特にゲームレビューでは、「課金」「イベント」「ガチャ」「容量」「アップデート」などが文脈によって意味を変えます。
たとえば、以下のように辞書を更新できます。
# ネガティブ寄りに扱いたい語
sentiment_dict.update({
"課金": -0.5,
"広告": -0.5,
"容量": -0.4,
})
# ポジティブ寄りに扱いたい語
sentiment_dict.update({
"イベント": 0.3,
"アップデート": 0.2,
"ストーリー": 0.5,
})ただし、辞書に追加する前に、実際のレビュー本文を確認することが重要です。たとえば「課金しなくても遊べる」はポジティブ寄りですが、「課金圧が強い」はネガティブ寄りです。同じ単語でも、文脈によって意味が変わります。
感情分析結果を見るときの注意点
辞書ベースの感情分析は手軽ですが、万能ではありません。結果を見るときは、以下の点に注意してください。
- 感情スコアは、レビュー内容の傾向を見るための参考値です。
- 辞書に含まれない表現はスコアに反映されません。
- 否定表現や皮肉表現を正確に扱うことは難しいです。
- 同じ単語でも、文脈によってポジティブにもネガティブにもなります。
- レビュー件数が少ない月は、月次スコアが大きくブレる場合があります。
- 星評価と感情スコアが一致しないレビューもあります。
- 最終的な解釈では、レビュー本文の確認が必要です。
感情スコアは、星評価やレビュー件数、テキスト分析、トピック抽出と組み合わせて見ることで、より解釈しやすくなります。
感情分析の活用例
Google Playレビューの感情分析は、以下のような用途に活用できます。
- アップデート後にネガティブな反応が増えたかを見る
- イベントやキャンペーン後にポジティブな反応が増えたかを見る
- 星評価の変化とレビュー本文の感情傾向を比較する
- 不満が増えた時期のレビュー本文を抽出して読む
- レビュー分析記事で、魅力や改善点を整理する補助に使う
たとえば、ネガティブ率が上がった月を特定し、その月の低評価レビューを抽出して読むことで、評価低下の背景を確認しやすくなります。
次に読む記事
- Google Playレビューのトピック抽出入門|LDAとBERTopicで話題を可視化する方法
- Google Playレビューのトピック構造を比較する方法|LDAとBERTopicの対応関係を可視化
- Google Playレビューのテキスト分析入門|ワードクラウド・共起ネットワーク・TF-IDFをPythonで可視化
- Google Playレビュー可視化入門|日次・月次の評価推移をPlotlyで分析する方法
- Google Playのデータ取得方法まとめ|google-play-scraperでアプリ情報・レビューを取得する
- Google Playレビュー分析まとめ
まとめ
この記事では、Google Playレビュー本文を使って、ポジティブ・ネガティブの傾向を感情辞書でスコア化し、月次推移として可視化する方法を紹介しました。
感情スコアを見ることで、アップデートやイベント後に好意的な反応が増えたのか、不満が増えたのかを補助的に確認できます。一方で、辞書ベースの分析は文脈理解に限界があるため、星評価、レビュー件数、レビュー本文、トピック抽出と組み合わせて解釈することが重要です。