AIの感情と感情分析レポート

Discourse AI プラグインには、コミュニティ全体の議論の感情的なトーンをより深く理解するのに役立つ感情分析機能が含まれています。このトピックでは、これらの AI 機能を活用して Discourse コミュニティに関する洞察を得るための、2 つの詳細なデータエクスプローラークエリの例について説明します。

  1. カテゴリと信頼レベル別の AI 感情合計: 特定のカテゴリと信頼レベル内での週ごとの感情の傾向を追跡する時系列分析
  2. AI 感情の異常値トピック: Discourse サイトで顕著な感情的反応を引き起こす議論トピックを特定します。

前提条件

これらのレポートを使用するには、以下が必要です。

  1. Discourse AI プラグインのインストールと有効化: Discourse AI プラグインがインスタンスにインストールされている必要があります
  2. 感情分析の有効化: Sentiment Analysis モジュールが設定され、アクティブである必要があります
  3. Data Explorer プラグイン: これらの SQL クエリを実行するために必要です
  4. 過去の感情データ: 意味のある結果を得るために十分な感情分析済み投稿(バックフィル操作が必要になる場合があります)

AI 感情モデルとその仕組み

レポートの詳細に入る前に、コミュニティの投稿でどの感情モデルが分析しているか理解しておくことが役立ちます。

これらのモデルは各投稿のテキストを分析し、その分類をデータベースに格納します。その後、Data Explorer プラグインによってクエリを実行できます。

カテゴリと信頼レベル別の AI 感情合計レポート

-- [params]
-- date :start_date = 2025-01-01
-- date :end_date = 2025-12-31
-- category_id :category_id = 6
-- int :min_trust_level = 0
-- boolean :exclude_staff = false

-- 指定されたカテゴリの週ごとの感情指標を集約する一時結果セットを作成
WITH sentiment_counts AS (
  SELECT 
    c.id as category_id,
    c.name as category_name,
    -- 時系列分析のために投稿を週ごとにグループ化
    DATE_TRUNC('week', p.created_at) as week_starting,
    EXTRACT(YEAR FROM p.created_at) as year,
    EXTRACT(WEEK FROM p.created_at) as week_number,
    -- ポジティブな感情の投稿数をカウント(閾値 > 0.6)
    COUNT(CASE WHEN (cr.classification::jsonb->'positive')::float > 0.6 THEN 1 
              ELSE NULL END) as positive_count,
    -- ネガティブな感情の投稿数をカウント(閾値 > 0.6)
    COUNT(CASE WHEN (cr.classification::jsonb->'negative')::float > 0.6 THEN 1 
              ELSE NULL END) as negative_count,
    -- ニュートラルな感情の投稿数をカウント(ポジティブとネガティブの両方が <= 0.6)
    COUNT(CASE WHEN (cr.classification::jsonb->'positive')::float <= 0.6 
               AND (cr.classification::jsonb->'negative')::float <= 0.6 THEN 1 
              ELSE NULL END) as neutral_count,
    -- 感情分析が施された投稿の総数
    COUNT(*) as total_classifications
  FROM classification_results cr
  -- 作成日とメタデータを取得するために投稿データを結合
  JOIN posts p ON p.id = cr.target_id AND cr.target_type = 'Post'
  -- カテゴリでフィルタリングするためにトピックデータを結合
  JOIN topics t ON t.id = p.topic_id
  -- 信頼レベルでフィルタリングするためにユーザーデータを結合
  JOIN users u ON u.id = p.user_id
  -- カテゴリ名を取得するためにカテゴリデータを結合
  JOIN categories c ON c.id = t.category_id
  WHERE 
    -- この特定のモデルからの感情結果のみを含める
    cr.model_used = 'cardiffnlp/twitter-roberta-base-sentiment-latest'
    -- 通常のトピックのみを含める(PM などは除く)
    AND t.archetype = 'regular'
    -- システム投稿を除外
    AND p.user_id > 0
    -- 選択されたカテゴリでフィルタリング
    AND c.id = :category_id
    -- 最小信頼レベルでフィルタリング
    AND u.trust_level >= :min_trust_level
    -- パラメータがチェックされている場合はスタッフユーザーを除外
    AND (:exclude_staff = false OR (u.admin = false AND u.moderator = false))
    -- 日付範囲でフィルタリング
    AND p.created_at BETWEEN :start_date AND :end_date
  -- 週とカテゴリでグループ化
  GROUP BY c.id, c.name, week_starting, year, week_number
)
-- 表示用に最終結果をフォーマット
SELECT 
  category_id,
  category_name,
  -- 表示をきれいにするために Date に変換
  week_starting::Date,
  -- ISO 週表記(YYYY-WXX)としてフォーマット
  year || '-W' || LPAD(week_number::text, 2, '0') as year_week,
  -- 正味の感情を計算(ポジティブからネガティブを引く)
  positive_count - negative_count as sentiment_balance,
  positive_count,
  negative_count,
  neutral_count,
  -- ポジティブな投稿の割合を計算(小数点以下 2 桁に丸める)
  ROUND(
    (positive_count::float / NULLIF(total_classifications, 0) * 100)::numeric,
    2
  ) as positive_percentage
FROM sentiment_counts
-- 時間の経過に伴う感情の傾向を示すために時系列でソート
ORDER BY week_starting ASC

このレポートは、特定のカテゴリ内の感情の傾向を週単位で分析し、以下を示します。

  • 週ごとのポジティブ、ネガティブ、ニュートラルな投稿数
  • 感情バランスの計算(ポジティブな投稿数からネガティブな投稿数を引いたもの)
  • 分析された投稿全体に対するポジティブな投稿の割合
  • ユーザーの信頼レベルによるフィルタリングとスタッフ投稿の除外オプション

このレポートは、以下に役立ちます。

  • 特定のカテゴリにおける時間の経過に伴うコミュニティの感情の傾向を追跡
  • 特定の出来事や変化と相関する可能性のあるコミュニティのムードの変動を特定
  • 信頼レベルによる異なるユーザーセグメント間での感情を比較
  • moderation の介入がコミュニティ全体の感情に与える影響を測定

パラメータ

クエリは分析をカスタマイズするためにいくつかのパラメータを受け入れます。

  • 日付範囲: 分析期間の開始日と終了日を設定
  • カテゴリ: 分析するカテゴリを選択
  • 最小信頼レベル: 特定の信頼レベル以上のユーザーからの投稿のみを含めるようにフィルタリング
  • スタッフを除外: 分析からスタッフの投稿を除外するオプション(通常のコミュニティメンバーに焦点を当てるため)

結果

結果は、各行がデータのある週を表すテーブル形式で表示されます。

  • カテゴリ情報: 分析されたカテゴリの ID と名前
  • 期間: 週の開始日と ISO 週表記(YYYY-WXX)
  • 感情指標:
    • 感情バランス: ポジティブな投稿とネガティブな投稿の差(正の値は全体的にポジティブな感情を示す)
    • ポジティブ/ネガティブ/ニュートラル数: 各感情カテゴリの投稿数
    • ポジティブな割合: ポジティブと分類された投稿の割合

結果の例

category_name week_starting year_week sentiment_balance positive_count negative_count neutral_count positive_percentage
Product Discussion 2025-01-06 2025-W01 -8 24 32 145 11.94
Product Discussion 2025-01-13 2025-W02 -11 30 41 210 10.68
Product Discussion 2025-01-20 2025-W03 -9 28 37 220 9.82
Product Discussion 2025-01-27 2025-W04 -13 33 46 260 9.74
Product Discussion 2025-02-03 2025-W05 -15 22 37 180 9.21
Product Discussion 2025-02-10 2025-W06 -6 37 43 195 13.45

AI 感情の異常値トピックレポート

-- [params]
-- date :start_date = 2025-01-01
-- date :end_date = 2025-12-31
-- category_id :category_id = 6
-- int :min_trust_level = 1
-- int :emotion_threshold = 10  

-- まず、トピックごとの感情的反応を集約する共通テーブル式(CTE)を作成
WITH topic_emotions AS (
  SELECT
    topics.id AS topic_id,                 -- 後での結合/フィルタリングのためにトピック ID を保存
    topics.title,                          -- 読みやすい結果のためにトピックタイトルを含める
    topics.created_at::date AS topic_date, -- トピック作成日を保存
    
    -- 各感情タイプごとに、その感情の信頼度が閾値 0.1 を超える投稿をカウント
    -- classification_results テーブルは感情スコアを JSON 値として格納
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'admiration')::float > 0.1) AS admiration_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'amusement')::float > 0.1) AS amusement_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'anger')::float > 0.1) AS anger_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'annoyance')::float > 0.1) AS annoyance_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'approval')::float > 0.1) AS approval_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'caring')::float > 0.1) AS caring_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'confusion')::float > 0.1) AS confusion_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'curiosity')::float > 0.1) AS curiosity_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'desire')::float > 0.1) AS desire_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'disappointment')::float > 0.1) AS disappointment_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'disapproval')::float > 0.1) AS disapproval_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'disgust')::float > 0.1) AS disgust_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'embarrassment')::float > 0.1) AS embarrassment_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'excitement')::float > 0.1) AS excitement_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'fear')::float > 0.1) AS fear_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'gratitude')::float > 0.1) AS gratitude_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'grief')::float > 0.1) AS grief_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'joy')::float > 0.1) AS joy_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'love')::float > 0.1) AS love_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'nervousness')::float > 0.1) AS nervousness_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'neutral')::float > 0.1) AS neutral_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'optimism')::float > 0.1) AS optimism_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'pride')::float > 0.1) AS pride_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'realization')::float > 0.1) AS realization_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'relief')::float > 0.1) AS relief_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'remorse')::float > 0.1) AS remorse_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'sadness')::float > 0.1) AS sadness_count,
    COUNT(*) FILTER (WHERE (classification_results.classification::jsonb->'surprise')::float > 0.1) AS surprise_count,
    
    -- ランキング用に感情的反応の総数を計算
    COUNT(*) AS total_emotional_reactions
  FROM
    classification_results
  -- 投稿メタデータを取得し、削除された投稿をフィルタリングするために posts テーブルと結合
  INNER JOIN
    posts ON posts.id = classification_results.target_id AND
    posts.deleted_at IS NULL               -- 削除された投稿を除外
  
  -- トピックメタデータを取得し、トピックタイプ/ステータスでフィルタリングするために topics テーブルと結合
  INNER JOIN
    topics ON topics.id = posts.topic_id AND
    topics.archetype = 'regular' AND       -- 標準的なトピックのみを含める(PM やシステムメッセージは除く)
    topics.deleted_at IS NULL              -- 削除されたトピックを除外
  
  -- 信頼レベルでフィルタリングするために users テーブルと結合
  INNER JOIN
    users ON users.id = posts.user_id
  
  WHERE
    -- 投稿(他のコンテンツタイプではない)の感情分類のみを含める
    classification_results.target_type = 'Post' AND
    
    -- この特定の感情検出モデルからの結果のみを使用
    classification_results.model_used = 'SamLowe/roberta-base-go_emotions' AND
    
    -- パラメータ化された値を使用して日付範囲でフィルタリング
    posts.created_at BETWEEN :start_date AND :end_date AND
    
    -- 指定されたカテゴリでフィルタリング
    (topics.category_id = :category_id) AND
    
    -- 十分な信頼レベルを持つユーザーからの投稿のみを含める
    (users.trust_level >= :min_trust_level)
    
  -- すべてのカウントをトピックごとにグループ化
  GROUP BY 
    topics.id, topics.title, topics.created_at::date
)

-- CTE から集約されたデータをフォーマットし、フィルタリングするメインクエリ
SELECT
  topic_id,                                -- トピック ID を表示(Discourse でリンクとしてレンダリングされます)
  --title,                                   -- トピックタイトルを表示
  topic_date,                              -- トピック作成日を表示
  total_emotional_reactions,               -- 検出された感情の総数を表示
  
  -- 重要な感情の配列をフォーマットされた文字列に変換
  -- 閾値を超えた感情のみが含まれ、それ以外は NULL となり省略されます
  -- 各感情は "EmotionName(count)" としてフォーマットされます
  ARRAY_TO_STRING(ARRAY[
    CASE WHEN admiration_count >= :emotion_threshold THEN 'Admiration(' || admiration_count || ')' ELSE NULL END,
    CASE WHEN amusement_count >= :emotion_threshold THEN 'Amusement(' || amusement_count || ')' ELSE NULL END,
    CASE WHEN anger_count >= :emotion_threshold THEN 'Anger(' || anger_count || ')' ELSE NULL END,
    CASE WHEN annoyance_count >= :emotion_threshold THEN 'Annoyance(' || annoyance_count || ')' ELSE NULL END,
    CASE WHEN approval_count >= :emotion_threshold THEN 'Approval(' || approval_count || ')' ELSE NULL END,
    CASE WHEN caring_count >= :emotion_threshold THEN 'Caring(' || caring_count || ')' ELSE NULL END,
    CASE WHEN confusion_count >= :emotion_threshold THEN 'Confusion(' || confusion_count || ')' ELSE NULL END,
    CASE WHEN curiosity_count >= :emotion_threshold THEN 'Curiosity(' || curiosity_count || ')' ELSE NULL END,
    CASE WHEN desire_count >= :emotion_threshold THEN 'Desire(' || desire_count || ')' ELSE NULL END,
    CASE WHEN disappointment_count >= :emotion_threshold THEN 'Disappointment(' || disappointment_count || ')' ELSE NULL END,
    CASE WHEN disapproval_count >= :emotion_threshold THEN 'Disapproval(' || disapproval_count || ')' ELSE NULL END,
    CASE WHEN disgust_count >= :emotion_threshold THEN 'Disgust(' || disgust_count || ')' ELSE NULL END,
    CASE WHEN embarrassment_count >= :emotion_threshold THEN 'Embarrassment(' || embarrassment_count || ')' ELSE NULL END,
    CASE WHEN excitement_count >= :emotion_threshold THEN 'Excitement(' || excitement_count || ')' ELSE NULL END,
    CASE WHEN fear_count >= :emotion_threshold THEN 'Fear(' || fear_count || ')' ELSE NULL END,
    CASE WHEN gratitude_count >= :emotion_threshold THEN 'Gratitude(' || gratitude_count || ')' ELSE NULL END,
    CASE WHEN grief_count >= :emotion_threshold THEN 'Grief(' || grief_count || ')' ELSE NULL END,
    CASE WHEN joy_count >= :emotion_threshold THEN 'Joy(' || joy_count || ')' ELSE NULL END,
    CASE WHEN love_count >= :emotion_threshold THEN 'Love(' || love_count || ')' ELSE NULL END,
    CASE WHEN nervousness_count >= :emotion_threshold THEN 'Nervousness(' || nervousness_count || ')' ELSE NULL END,
    CASE WHEN optimism_count >= :emotion_threshold THEN 'Optimism(' || optimism_count || ')' ELSE NULL END,
    CASE WHEN pride_count >= :emotion_threshold THEN 'Pride(' || pride_count || ')' ELSE NULL END,
    CASE WHEN realization_count >= :emotion_threshold THEN 'Realization(' || realization_count || ')' ELSE NULL END,
    CASE WHEN relief_count >= :emotion_threshold THEN 'Relief(' || relief_count || ')' ELSE NULL END,
    CASE WHEN remorse_count >= :emotion_threshold THEN 'Remorse(' || remorse_count || ')' ELSE NULL END,
    CASE WHEN sadness_count >= :emotion_threshold THEN 'Sadness(' || sadness_count || ')' ELSE NULL END,
    CASE WHEN surprise_count >= :emotion_threshold THEN 'Surprise(' || surprise_count || ')' ELSE NULL END
  ], ', ', '') AS significant_emotions     -- カンマ区切りで結合、区切り文字が不要な場合は空文字列
  
FROM 
  topic_emotions
WHERE 
  -- 少なくとも 1 つの感情が閾値を超えているトピックのみを含める
  -- これは顕著な感情的影響を持つトピックを特定します
  (
    admiration_count >= :emotion_threshold OR
    amusement_count >= :emotion_threshold OR
    anger_count >= :emotion_threshold OR
    annoyance_count >= :emotion_threshold OR
    approval_count >= :emotion_threshold OR
    caring_count >= :emotion_threshold OR
    confusion_count >= :emotion_threshold OR
    curiosity_count >= :emotion_threshold OR
    desire_count >= :emotion_threshold OR
    disappointment_count >= :emotion_threshold OR
    disapproval_count >= :emotion_threshold OR
    disgust_count >= :emotion_threshold OR
    embarrassment_count >= :emotion_threshold OR
    excitement_count >= :emotion_threshold OR
    fear_count >= :emotion_threshold OR
    gratitude_count >= :emotion_threshold OR
    grief_count >= :emotion_threshold OR
    joy_count >= :emotion_threshold OR
    love_count >= :emotion_threshold OR
    nervousness_count >= :emotion_threshold OR
    optimism_count >= :emotion_threshold OR
    pride_count >= :emotion_threshold OR
    realization_count >= :emotion_threshold OR
    relief_count >= :emotion_threshold OR
    remorse_count >= :emotion_threshold OR
    sadness_count >= :emotion_threshold OR
    surprise_count >= :emotion_threshold
  )
-- 最も感情的な反応が多い順に結果をソート
ORDER BY
  total_emotional_reactions DESC

このレポートは、以下の基準に基づいてコミュニティで顕著な感情的反応を引き起こしたトピックを特定します。

  • トピック内の投稿で検出された各感情タイプの数
  • 「顕著な」感情的反応を決定するための設定可能な閾値
  • カテゴリ、日付範囲、ユーザーの信頼レベルによるフィルタリング

このレポートは、以下に役立ちます。

  • 問題になる前に強いネガティブな感情を生み出している可能性のある議論を特定
  • コミュニティと感情的に共鳴する非常に魅力的なコンテンツを見つける
  • 拡大する前に moderation の注意が必要かもしれないトピックを検出
  • 特定の感情的反応を引き起こすコンテンツのテーマを発見
  • コミュニティで感情的な関与を促進する要因をより深く理解

パラメータ

クエリはいくつかのパラメータを受け入れます。

  • 日付範囲: 分析期間の開始日と終了日を設定
  • カテゴリ: 分析するカテゴリを選択
  • 最小信頼レベル: 特定の信頼レベル以上のユーザーからの投稿のみを含めるようにフィルタリング
  • 感情閾値: 感情を「顕著」と見なすために必要なインスタンス数を設定

結果

結果は以下を表示します。

  • トピック ID: トピックに直接リンク(Data Explorer でクリック可能)
  • トピック日: トピックが作成された日
  • 感情的反応の総数: 検出された感情的反応の総数
  • 重要な感情: 閾値を超えた感情のフォーマットされたリスト(括弧内にカウントが表示)

検出される感情には、以下のような幅広い種類が含まれます:賞賛、楽しみ、怒り、迷惑、承認、思いやり、混乱、好奇心、欲望、失望、非難、嫌悪、恥ずかしさ、興奮、恐怖、感謝、悲嘆、喜び、愛、緊張、中立、楽観、誇り、気づき、安堵、後悔、悲しみ、驚き。

結果の例

topic topic_date total_emotional_reactions significant_emotions
Feature Request: Increased API Rate Limits 2025-03-06 42 Approval(15), Confusion(9), Curiosity(7), Gratitude(8)
Authentication Error with Third-Party Integration 2025-01-07 33 Curiosity(6), Gratitude(5), Disapproval(8), Frustration(9)
Best Practices for Configuration Settings 2025-02-16 31 Curiosity(9), Excitement(6), Gratitude(5), Optimism(5)
Troubleshooting Database Connection Issues 2025-01-15 29 Curiosity(7), Confusion(8), Disappointment(6), Frustration(5)
Critical Bug in Latest Beta Release 2025-02-02 26 Confusion(7), Concern(6), Disapproval(5), Urgency(6)

コミュニティ管理における実用的な応用

これらのレポートは、コミュニティ管理のワークフローを以下のように強化できます。

  • 早期介入: 問題になる前に moderation が必要かもしれない感情的に charged なトピックを特定
  • コンテンツ計画: ポジティブな感情を引き起こすものに関する洞察を活用してコンテンツ戦略を策定
  • 影響の測定: 方針の変更、新機能、イベントがコミュニティの感情に与える影響を評価
  • ターゲットエンゲージメント: 公式の応答が役立つ可能性のある強い感情的反応を持つトピックにスタッフの注意を集中

追加リソース

「いいね!」 1