ソーシャルメディアデータ ── SNSから得られる非構造化データ
近年の社会科学・マーケティング論文で頻出。 「リアルタイムの世論」「災害時の情報拡散」など、 公式統計では捉えられない動きを観測できます。
SNSデータの3つの層:
これらを組み合わせて、 「誰が、 何を、 どう広めたか」を分析できます。
架空シナリオ:「災害時のSNS反応」
最小限のスニペットで動作確認できる例。 公的データ(SSDSE 等)を想定しています。
'高齢化 lang:ja'、 上限 100 件。 tweet_fields=['created_at','public_metrics'] でメタ情報を付与。1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # X (Twitter) API v2 の例(tweepyライブラリ) import tweepy import pandas as pd client = tweepy.Client(bearer_token='YOUR_BEARER_TOKEN') res = client.search_recent_tweets(query='高齢化 lang:ja', max_results=100, tweet_fields=['created_at','public_metrics']) df = pd.DataFrame([{ 'text': t.text, 'created_at': t.created_at, 'likes': t.public_metrics['like_count'] } for t in res.data]) print(df.head()) |
テキストデータの基本処理を、 SSDSE のような数値データと組み合わせる典型例を示します。 仮想的に「都道府県名を含むツイートの感情極性」を集計するシナリオです。
data/raw/tweets_sample.csv (想定列:text, created_at, prefecture)。 parse_dates で datetime 化。 SSDSE と結合するキーは「prefecture」(都道府県名)。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd import re # (A) ツイート風のテキストを読み込み(CSV列: text, created_at, prefecture) tweets = pd.read_csv('data/raw/tweets_sample.csv', encoding='utf-8', parse_dates=['created_at']) print(tweets.shape, tweets.dtypes) # (B) 前処理:URL/メンション/ハッシュタグの除去 def clean_text(s: str) -> str: s = re.sub(r'https?://\S+', '', s) # URL s = re.sub(r'@\w+', '', s) # メンション s = re.sub(r'#(\w+)', r'\1', s) # ハッシュタグの#を除く return s.strip() tweets['clean'] = tweets['text'].astype(str).apply(clean_text) print(tweets[['text','clean']].head(3)) |
感情極性は辞書ベースが導入として手軽:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | POS = {'良い','嬉しい','最高','素晴らしい','楽しい'} NEG = {'悪い','悲しい','最悪','酷い','嫌い'} def polarity(s: str) -> int: pos = sum(w in s for w in POS) neg = sum(w in s for w in NEG) return pos - neg # 正:ポジ / 負:ネガ / 0:中立 tweets['polarity'] = tweets['clean'].apply(polarity) # (C) 都道府県 × 日次で集計 daily = (tweets .groupby([tweets['created_at'].dt.date, 'prefecture']) ['polarity'] .agg(['mean','count']) .reset_index()) print(daily.head()) |
SSDSE と結合すれば「人口規模 vs ツイート量・感情」の関係も検証可能:
1 2 3 4 5 6 7 | ssdse = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) ssdse = ssdse[['都道府県','人口総数']].rename(columns={'都道府県':'prefecture'}) agg = (tweets.groupby('prefecture')['polarity'].mean() .reset_index(name='avg_polarity')) merged = agg.merge(ssdse, on='prefecture', how='inner') print(merged.corr(numeric_only=True)) |
| プラットフォーム | 主な投稿形式 | ユーザー層 | 分析適性 | 取得難度 |
|---|---|---|---|---|
| X (Twitter) | 短文+画像 | 幅広いが社会的関心が高い層 | 時事・速報 | 高(API 有料化) |
| YouTube | 動画+コメント | 10–50代中心 | コンテンツ評価 | 中 |
| 長文+投票 | 欧米中心、専門コミュニティ | 深掘り議論分析 | 低 | |
| 画像・短編動画 | 10–30代 | ブランド・マーケ | 中 | |
| TikTok | 短編動画 | Z世代中心 | トレンド検知 | 高 |
| Mastodon | 短文+画像 | 技術寄りニッチ | 研究用途 | 低 |
| Bluesky | 短文+画像 | 早期採用者 | 新興分析 | 中(成長中) |
「SNS で多い意見=世論」ではありません。 次のバイアスを必ず文書化:
対策としては、 ① 公的統計や調査データと併用、 ② プラットフォーム別の比較、 ③ ユーザー特性で層別化、 ④ ボット検出フィルタ、 などが有効です。
Q1. 学術利用なら API 無料枠で十分?
X はかつて Academic Research Track があったが現状大きく制限。 Reddit / Mastodon / Bluesky は緩やかで、 大学研究には現実的。 古いデータが必要なら学術データセット(GDELT 等)を検討。
Q2. 感情分析は辞書 vs 機械学習、どちらを使う?
初手は辞書(実装が単純)。 ニュアンスや皮肉が問題になるなら、 既訓練の BERT 系(日本語なら東北大ベース、 cl-tohoku/bert-base-japanese)か LLM API。 ただし計算コストとの兼ね合い。
Q3. 個人を特定できる投稿は引用してよい?
原則 No。 ID をハッシュ化または完全匿名化し、 引用が必要な場合は IRB 審査と本人同意が望ましい。 公人の公的発言は別ですが慎重に。
Q4. ハッシュタグ分析だけで十分か?
代表性が悪いことが多い。 ハッシュタグ非使用の投稿が大多数で、 ハッシュタグ付き投稿はキャンペーン参加者など特殊層に偏る。 補助的指標として使う。
Q5. 位置情報付き投稿の割合は?
プラットフォーム・年代により 1〜5% 程度。 災害分析等で重要だが、 自己選択バイアスが強いため数値的代表性は乏しい。 補完にプロフィール記載地域などを使う。
| 観点 | SNS データ | SSDSE / e-Stat |
|---|---|---|
| 取得方法 | API・スクレイピング | CSV ダウンロード |
| 時間粒度 | 秒オーダーまで可 | 年次・月次中心 |
| 空間粒度 | 位置情報があれば点単位 | 都道府県・市区町村 |
| 代表性 | 低(自己選択) | 高(悉皆/確率標本) |
| 変数 | テキスト・画像中心 | 数値中心 |
| 分析適性 | 感情・トレンド・拡散 | 構造・規模・推移 |
| 補完関係 | 「いま起きていること」 | 「全体像」 |
両者を組合せた研究が増えています。 例:SNS の感情指標 × 県別失業率(SSDSE)の相関、 災害発生時の SNS 言及量 × 人的被害(消防庁統計)の関係など。
SNS データは「テキスト+メタ+画像/動画リンク」と階層化されており、 単一の表に押し込めると非効率です。 典型的な3層設計:
投稿 ID(UNIQUE)と取得時刻(パーティション)が中核キー。 ハッシュタグやメンションは JSON 配列 → 配列展開ビューで結合。 グラフ部分(フォロー / RT)は Neo4j などのグラフ DB が向きます。
| 領域 | 代表タスク | 活用される指標・手法 |
|---|---|---|
| マーケティング | ブランド評判・キャンペーン効果 | SOV、 NSS、 ハッシュタグ参加数 |
| 公共政策 | 政策に対する反応・分極化分析 | ネットワーククラスタリング、 感情分析 |
| 災害対応 | 避難情報拡散、 被災状況把握 | 位置情報集約、 緊急ハッシュタグ追跡 |
| 金融 | 市場感情と株価の関係 | VADER、 ニュース感情指標 |
| 公衆衛生 | 疾病流行の早期検知 | 症状ハッシュタグの時空間集約 |
| 社会科学 | 世論形成・抗議運動・分極化 | トピックモデル、 グラフ分析 |
| 教育 | 学習者コミュニティの態度分析 | コメント分類、 感情分析 |
Reddit (PRAW):研究用途で最も親切な API。 OAuth でアプリ登録するだけ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import praw, pandas as pd reddit = praw.Reddit(client_id='YOUR_ID', client_secret='YOUR_SECRET', user_agent='research-bot v0.1') posts = [] for s in reddit.subreddit('japan').hot(limit=200): posts.append({'id': s.id, 'title': s.title, 'score': s.score, 'created_utc': s.created_utc, 'num_comments': s.num_comments, 'flair': s.link_flair_text}) df = pd.DataFrame(posts) df['created_at'] = pd.to_datetime(df['created_utc'], unit='s', utc=True) print(df.head()) |
YouTube Data API v3:動画コメントを取得。 クォータ単位で課金。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from googleapiclient.discovery import build import pandas as pd yt = build('youtube', 'v3', developerKey='YOUR_KEY') req = yt.commentThreads().list(part='snippet', videoId='VIDEO_ID', maxResults=100, textFormat='plainText') res = req.execute() comments = [{ 'author': it['snippet']['topLevelComment']['snippet']['authorDisplayName'], 'text': it['snippet']['topLevelComment']['snippet']['textDisplay'], 'likes': it['snippet']['topLevelComment']['snippet']['likeCount'], 'published': it['snippet']['topLevelComment']['snippet']['publishedAt'], } for it in res['items']] print(pd.DataFrame(comments).head()) |
Mastodon:分散インスタンスごとに API があり、 認証不要の public ストリームも利用可。
1 2 3 4 5 6 7 8 | from mastodon import Mastodon m = Mastodon(api_base_url='https://mastodon.social') # パブリックタイムライン toots = m.timeline_public(limit=40) for t in toots[:5]: print(t['content'][:80].replace('<','<')) |
SNS テキストの分析に頻出する技術と日本語用のおすすめライブラリ:
| タスク | 手法 | 日本語向け実装 |
|---|---|---|
| 形態素解析 | 単語分割・品詞推定 | MeCab, Janome, fugashi+UniDic, SudachiPy |
| 基本ベクトル化 | TF-IDF, Bag-of-Words | scikit-learn TfidfVectorizer |
| 単語埋め込み | Word2Vec, fastText | gensim, Wikipedia 学習済モデル |
| 文埋め込み | BERT, Sentence-BERT | cl-tohoku/bert-base-japanese (HF) |
| 感情分析 | 辞書 / 機械学習 / LLM | 日本語評価極性辞書, asari, BERT |
| トピック抽出 | LDA, BERTopic, NMF | gensim LdaModel, bertopic |
| 固有表現抽出 | NER(人名/組織/地名) | GiNZA, spaCy 日本語モデル |
| 対話/要約 | GPT, T5 | OpenAI / Claude API, Llama日本語版 |
| 指標 | 計算式 | 意味 |
|---|---|---|
| エンゲージメント率 | (Like+RT+Reply) / Followers | フォロワー比の反応強度 |
| バイラル係数 | $R_0 = \beta/\gamma$ | 1超で拡大、1未満で収束 |
| SOV (Share of Voice) | 対象ブランド言及数 / 全言及数 | 市場の声占有率 |
| NSS | (ポジ件数 − ネガ件数) / 全件 | 純感情スコア |
| 影響度 | $\log$(Followers) × Engagement | 単発リーチの規模 |
| バースト度 | $|\Delta f|$ / 過去窓の平均 | 話題の急上昇度 |
2010 年代前半、 Twitter の言及量から選挙結果を予測する論文が多数発表されました。 しかし 2016 年米大統領選では、 SNS 上では Clinton 支持の言及がトランプを大きく上回ったにもかかわらず、 トランプが勝利。 原因は 選択バイアス(Twitter ユーザーは民主党支持に偏在)と、 「沈黙の螺旋」(少数派は発言を控える)でした。
教訓:① SNS データ単独で集団全体を推定しない、 ② 公的調査と必ず突き合わせる、 ③ 「ない人」の声を補完する設計(重み付け、補完サンプリング)が必要。
SNS 上の情報拡散は、 感染症の SIR モデルと近い構造を持ちます:
$$ \frac{dI}{dt} = \beta S I - \gamma I $$
$S$=未拡散ユーザー、 $I$=拡散済み、 $\beta$=拡散率、 $\gamma$=飽和率。 基本再生産数 $R_0 = \beta / \gamma$ が 1 を超えれば バイラル化。 ハッシュタグの時系列を当てはめると流行の最大規模を予測できます。
独立カスケード(IC)モデル、 線形閾値モデルなど、 ネットワーク上の拡散モデルが多数提案されています。 ネットワーク中心性(degree, betweenness, PageRank)が高いノードを特定すると、 効率的な情報伝搬経路や、 偽情報の発信源候補を見つけられます。
| プラットフォーム | 取得方法 | 制限 |
|---|---|---|
| X (Twitter) | API v2(有料化)、 Academic Track | 厳しいレート制限 |
| YouTube | Data API v3 | クォータあり |
| PRAW(Python) | 比較的緩い | |
| Mastodon | 公開API | 各インスタンスのポリシー |
| Bluesky | ATP / AT Protocol | 新興、 制限緩め |
| プラットフォーム | 取得方法 | 制限 |
|---|---|---|
| X (Twitter) | API v2(有料化)、 Academic Track | 厳しいレート制限 |
| YouTube | Data API v3 | クォータあり |
| PRAW(Python) | 比較的緩い | |
| Mastodon | 公開API | 各インスタンスのポリシー |
| Bluesky | ATP / AT Protocol | 新興、 制限緩め |