ラベル データサイエンス の投稿を表示しています。 すべての投稿を表示
ラベル データサイエンス の投稿を表示しています。 すべての投稿を表示

2023年6月7日水曜日

自分のツイートを分析してみた3

自分のツイートを分析してみた3

概要

前回の続きで 30,000 ツイートに達したのでデータ分析してみました

環境

  • Ruby 3.2.1

全ツイート数

  • 30220

全ツイートをどれくらいの期間で行ったか

  • 2016/03/24 - 2023/05/31

2625 日分のツイートになります

月単位でどれくらいツイートを行ったか

月間最高ツイートは 2019/06 で 846 ツイートでした (前回と変わらず)

日単位でどれくらいツイートを行ったか

日で最高ツイートは 2018/06/06 で 156 ツイートでした (前回と変わらず)

月単位でどれくらいリツイートをされたか

最高でも月で 4 リツイートが一番多かったです (前回から減っているので取り消しされてるっぽい)

月単位でどれくらいファボをされたか

月間最高ファボは 2021/10 で 41 ファボでした (前回から減っているので取り消しされてるっぽい)

ツイートしているデバイスの割合

ほぼブラウザからツイートでした (前回とほぼ同じ)

よくツイートされている単語 Top100

  • t -> 1990
  • co -> 1952
  • https -> 1780
  • ー -> 1223
  • 感じ -> 1146
  • アプリ -> 1002
  • 方法 -> 920
  • 人 -> 779
  • 自分 -> 744
  • ファイル -> 466
  • インストール -> 434
  • あと -> 402
  • docker -> 395
  • ゲーム -> 357
  • コード -> 346
  • 情報 -> 343
  • 久しぶり -> 326
  • gt -> 321
  • コンテナ -> 318
  • データ -> 309
  • サーバ -> 305
  • アカウント -> 286
  • API -> 277
  • 記事 -> 272
  • 自動 -> 260
  • ruby -> 258
  • コマンド -> 258
  • s -> 254
  • mac -> 253
  • 気 -> 234
  • ポイント -> 206
  • 別 -> 206
  • 環境 -> 205
  • D -> 197
  • 動画 -> 195
  • デフォルト -> 189
  • k -> 185
  • 画面 -> 180
  • ログイン -> 179
  • 雨 -> 175
  • ビルド -> 173
  • S -> 172
  • 複数 -> 171
  • ユーザ -> 164
  • ケース -> 161
  • バージョン -> 160
  • ドメイン -> 159
  • 名前 -> 158
  • ツール -> 157
  • ネットワーク -> 155
  • カード -> 153
  • web -> 152
  • 個人 -> 152
  • 他 -> 151
  • アップデート -> 151
  • レベル -> 148
  • サイト -> 147
  • ページ -> 147
  • イベント -> 142
  • 最後 -> 142
  • デプロイ -> 141
  • google -> 139
  • ホスト -> 139
  • 野球 -> 138
  • ブログ -> 137
  • クライアント -> 137
  • 子供 -> 137
  • ウォーク -> 137
  • 月 -> 136
  • iPhone -> 134
  • モード -> 133
  • ライブラリ -> 132
  • d -> 132
  • p -> 132
  • クラス -> 132
  • 状態 -> 131
  • 次 -> 127
  • 無料 -> 126
  • バグ -> 126
  • python -> 126
  • バッテリー -> 125
  • Ruby -> 125
  • パスワード -> 122
  • 文字 -> 122
  • Mac -> 121
  • プロ -> 120
  • ネタ -> 120
  • 娘 -> 118
  • マシン -> 116
  • 変数 -> 116
  • 時代 -> 116
  • どん -> 114
  • ドキュメント -> 113
  • 仕組み -> 113
  • iOS -> 112
  • wifi -> 111
  • 回線 -> 110
  • Google -> 109
  • メソッド -> 108
  • ローカル -> 108

最後に

40,000 ツイートしたら再度解析してみたいと思います フォロワー数は若干増えています

参考サイト

2023年6月5日月曜日

自分のツイートからマルコフ連鎖で文章を自動生成してみる

自分のツイートからマルコフ連鎖で文章を自動生成してみる

概要

3万件ほどある自分の Twitter のアーカイブデータからマルコフ連鎖を使って文章を自動生成してみました

環境

  • Ubuntu 18.04
    • mecab 0.996
  • Python 3.10.2
    • mecab-python3 1.0.6
    • markovify

Twitter アーカイブデータの取得

Twitter の Web 画面から行います
データ量によっては 1日以上かかる場合もあります
ダウンロードできたら解凍しその中にある tweets.js を保存しておきます

ちなみに自分のツイートは執筆時点で 30220 ツイートありました

ツールインストール

  • sudo apt -y install mecab mecab-ipadic-utf8
  • sudo cp /etc/mecabrc /usr/local/etc/mecabrc
  • mecab -D
filename:       /var/lib/mecab/dic/debian/sys.dic
version:        102
charset:        UTF-8
type:   0
size:   392126
left size:      1316
right size:     1316

ライブラリインストール

  • pipenv install mecab-python3 markovify

サンプルコード

とりあえず動かすのが目的なのでクラス化などのリファクタリングはしていません

  • vim app.py
import markovify
import MeCab
import json

# tweets.js を読み込んでツイート情報を配列に格納
tweets = []
with open("./tweets.js", mode="r") as f:
    for tweet in json.loads(f.read().replace('window.YTD.tweets.part0 = ', '')):
        tweets.append(tweet['tweet']['full_text'])

# ツイートの配列情報を1行ごとにしてテキストファイルに保存
print(len(tweets))
with open('./input.txt', mode='w') as f:
    for tweet in tweets:
        f.write(f"{tweet}\n")

# 1行ごとに保存したツイート情報をmecabを使って分かち書きに変換、分かち書きも1ツイート1行にして保存
parsed_text = ""
with open("./input.txt", mode="r") as f:
    for line in f.read().split():
        tagger = MeCab.Tagger("-Owakati")
        parsed_text += f"{tagger.parse(line).strip()}\n"

# 分かち書きを1行ごとに保存したのでNewlineText を使って学習
text_model = markovify.NewlineText(parsed_text, state_size=1)

# 文章を10個生成してみる
for _ in range(10):
    sentence = text_model.make_short_sentence(100, tries=100)
    if sentence is None:
        continue
    print(sentence)

動作確認

  • pipenv run python app.py
プラットフォーム なんて やっ て みる か ー 、 あきらめる か 、 、 、 やっぱり 筋 トレ 始め たら 逆 に
の 調子 が 読み込ま れ て いる みたい に 含ま れ ちゃう だろ う か 、 、
を 要求 さ
もう 忘れ て ほしかっ た ほう が 少ない から コロナ は ステート フル カラー ボックス ない や で ない の か な 。 もはや 顔出し し たい 場合 は
うーん 明日 から 届か ない 気 が 多い の 見る の 圧力 センサー 系 も 夢 は
300 H 1 . co / LRgfICHpN 0 ZVHy
で 使っ て から 完全 に 見 た ほう が 自分 だけ で も 試し た 事 象っ て も 会わ ず 、 、
background - server
とか なる 可能 性 が ある か 、
を 送り 続け ない ん ? これ は タッチバーモデル しか 見 てる の ほう が 、

正直意味不明ですがツイートと言えばツイートっぽい気もします
URL なども学習させているのでこのあたりをクリーニングすればもっと良くなるかもです

トラブルシューティング: UnicodeEncodeError: ‘utf-8’ codec can’t encode characters in position 0-1: surrogates not allowed

mecab はデフォルトだと euc_jp なので python の文字コードと違うがあるため print などでエラーになっています
mecab-ipadic-utf8 をインストールし直しましょう

最後に

パラメータなどを調整したりデータのクリーニングをしたりすればもっと良くなると思います 内部的な使用は不明ですがデフォルトだと分かち書きをしたデータをただランダムに組み合わせているレベルにしかならないのかもしれません

参考サイト

2021年1月15日金曜日

自分のツイートを分析してみた2

概要

前回の続きで 20,000 ツイートに達したのでデータ分析してみました

環境

  • Ruby 3.0.0p0

全ツイート数

  • 21858

全ツイートをどれくらいの期間で行ったか

  • 2016/03/24 - 2021/01/12

1755 日分のツイートになります

月単位でどれくらいツイートを行ったか

月間最高ツイートは 2019/06 で 846 ツイートでした

日単位でどれくらいツイートを行ったか

日で最高ツイートは 2018/06/06 で 156 ツイートでした

月単位でどれくらいリツイートをされたか

最高でも月で 5 リツイートが一番多かったです

月単位でどれくらいファボをされたか

月間最高ファボは 2020/04 で 43 ファボでした

ツイートしているデバイスの割合

ほぼブラウザからツイートでした

よくツイートされている単語 Top100

  • t -> 1418
  • co -> 1394
  • https -> 1263
  • 感じ -> 865
  • ー -> 803
  • アプリ -> 793
  • 人 -> 616
  • 方法 -> 580
  • 自分 -> 563
  • ファイル -> 383
  • インストール -> 327
  • docker -> 322
  • あと -> 301
  • gt -> 292
  • 情報 -> 287
  • コンテナ -> 282
  • ゲーム -> 280
  • API -> 255
  • サーバ -> 249
  • アカウント -> 244
  • データ -> 243
  • コード -> 242
  • コマンド -> 224
  • 久しぶり -> 224
  • 記事 -> 223
  • 自動 -> 211
  • ruby -> 206
  • 気 -> 195
  • mac -> 187
  • s -> 181
  • 環境 -> 173
  • 別 -> 172
  • 動画 -> 170
  • 画面 -> 155
  • デフォルト -> 151
  • ポイント -> 150
  • ログイン -> 146
  • ドメイン -> 141
  • 複数 -> 139
  • ビルド -> 138
  • 名前 -> 133
  • ネットワーク -> 133
  • D -> 131
  • ユーザ -> 130
  • 個人 -> 129
  • k -> 128
  • ブログ -> 126
  • ツール -> 125
  • ページ -> 124
  • バージョン -> 123
  • アップデート -> 123
  • ケース -> 122
  • web -> 121
  • p -> 121
  • サイト -> 121
  • d -> 120
  • レベル -> 120
  • ホスト -> 119
  • google -> 116
  • ライブラリ -> 115
  • 他 -> 113
  • iPhone -> 108
  • モード -> 108
  • イベント -> 107
  • どん -> 106
  • 状態 -> 106
  • Mac -> 104
  • カード -> 104
  • 月 -> 104
  • デプロイ -> 103
  • wifi -> 102
  • クライアント -> 102
  • 文字 -> 101
  • UI -> 101
  • 変数 -> 98
  • 回線 -> 97
  • iOS -> 97
  • 次 -> 96
  • バグ -> 95
  • 仕組み -> 95
  • Ruby -> 93
  • 時代 -> 93
  • S -> 93
  • Google -> 93
  • 無料 -> 92
  • 最後 -> 92
  • タグ -> 92
  • or -> 90
  • ドキュメント -> 90
  • swift -> 89
  • コール -> 88
  • マイクラ -> 87
  • ローカル -> 87
  • クラス -> 87
  • 野球 -> 86
  • メソッド -> 86
  • golang -> 86
  • スタンプ -> 85
  • パスワード -> 84
  • 部分 -> 82

["ポイント", "d", "モード", "どん", "カード", "wifi", "回線", "バグ", "仕組み", "Ruby", "S", "最後", "コール", "野球", "パスワード"] が新規で登場していました

逆に ["Arduino", "xcode", "mini", "swagger", "画像", "端末", "プロジェクト", "Swift", "js", "使い方", "python", "ポート", "r", "デバイス", "ボタン"] が前回の Top100 からなくなっていました

最後に

30,000 ツイートしたら再度解析してみたいと思います
ちなみにフォロワー数はずっと横ばいです

参考サイト

2019年2月17日日曜日

コサイン類似度を学ぶ

概要

前回、自分のツイートの TFIDF を算出してツイート内から特徴語を算出してみました
今回はさらに算出した TFIDF を使って各ツイートの類似度をコサイン類似度をクラスタリングしてみたいと思います

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

コサイン類似度を算出するスクリプト

前回、算出した各ツイートの単語ごとの TFIDF の結果を読み込んで算出します

  • bundle init
  • vim Gemfile
gem "natto"
  • bundle install --path vendor
  • vim cosine_similarity.rb
require 'json'

class CosSim
  attr_accessor :all_tweet_count

  def initialize
    file = 'results.json'
    @data = JSON.parse(File.read(file))
    @all_tweet_count = @data.size
    @results = []
  end

  def calc(index: 0)
    @source = @data['results'][index]
    @data['results'].each_with_index { |target, i|
      r = Result.new(target['tweet'], cos(target))
      @results.push(r)
    }
  end

  def clear
    @results.clear
  end

  def show_result(top = 10)
    ret = {}
    ret.store(:source, @source['tweet'])
    rr = []
    sorted = @results.sort { |a, b| b.cos <=> a.cos }.first(top)[1..-1]
    sorted.each { |result|
      r = {}
      r.store(:target, result.tweet)
      r.store(:cos, result.cos)
      rr.push(r)
    }
    ret.store(:result, rr)
    puts ret.to_json
  end

  def cos(target)
    term_dimensions = []
    @source['terms'].each { |t|
      term_dimensions.push(t['term']) unless term_dimensions.include?(t['term'])
    }
    target['terms'].each { |t|
      term_dimensions.push(t['term']) unless term_dimensions.include?(t['term'])
    }
    source_tfidf_map = []
    target_tfidf_map = []
    term_dimensions.each { |dim|
      sterm = @source['terms'].select { |t| t['term'] == dim }.first
      tterm = target['terms'].select { |t| t['term'] == dim }.first
      if sterm.nil?
        source_tfidf_map.push(0.0)
      else
        source_tfidf_map.push(sterm['tfidf'].to_f)
      end
      if tterm.nil?
        target_tfidf_map.push(0.0)
      else
        target_tfidf_map.push(tterm['tfidf'].to_f)
      end
    }
    a = Math.sqrt(source_tfidf_map.inject(0){ |sum, tfidf| sum + tfidf * tfidf })
    b = Math.sqrt(target_tfidf_map.inject(0){ |sum, tfidf| sum + tfidf * tfidf })
    ab = (target_tfidf_map.zip(source_tfidf_map).map { |t, s| t * s }).sum
    cos = ab / (a * b)
  end

  class Result
    attr_accessor :tweet, :cos

    def initialize(tweet, cos)
      @tweet = tweet
      @cos = cos
    end
  end
end

cs = CosSim.new
10.times do
  i = Random.rand(0..10015)
  cs.calc(index: i)
  cs.show_result(4)
  cs.clear
end

少し解説

そもそもコサイン類似度は「ある文書 (ここではツイート) とある文書が似ている文書なのか」を算出することができる計算方法です
ツイートは全部で 10,000 ほどあるので単純にすべてのツイートに対してコサイン類似度を出そうとすると 10,000 * 10,000 = 1億回 の処理が必要になります
時間があるのであれば放置しておけばいいですが、そうもいきません
上記のスクリプトは cs.calc(index: 100) で対象のツイートのインデックスを指定することできるのであるツイートに対して類似するツイートを 10,000 件の中から探すようになっています
もし全件やりたい場合は all_tweet_count 分メイン処理でループさせれば OK です

処理の流れとしてまず類似度を比較する文書 2 つの中に出現する単語のベクトルを作成します
ベクトルの大きさは TFIDF を採用します
イメージ的には以下の通りです

文書 ギガ 使用
ツイート1 0.38 0 0.18 0.13
ツイート2 0.22 0 0.01 0

そのためにまず文書間で出現する単語のユーニク (term_dimensions) を算出します
そしてその単語ごとに前回の結果から TFIDF を算出し source_tfidf_maptarget_tfidf_map に追加します
単語が出現しない場合は 0.0 を追加します

あとは作成したそれぞれの map からコサイン類似度の計算に必要な値を算出します
Math.sqrt(source_tfidf_map.inject(0){ |sum, tfidf| sum + tfidf * tfidf }) は各単語の tfidf を二乗して、それらの sum を計算しています
更に計算された合計に対して平方根をとります
それを同様に target_tfidf_map でも行います

(target_tfidf_map.zip(source_tfidf_map).map { |t, s| t * s }).sum はそれぞれのマップの各要素の tfidf の積を計算しそれを sum しています

あとはこれらの値からコサイン類似度を求めます (cos = ab / (a * b))
ここで算出された値は -1 から 1 の間を取ります
1 に近ければ近いほど類似度が高くなります

結果を少し見てみる

先程も述べたようにすべてのツイートの類似度を見るのは時間がかかるので適当にサンプリングして見てみました

例えば以下の結果は「元気」というワードがどのツイートにも出てきており「似ていると言えば似ているな、、、」という感じなのがわかります
cos の数値を見ても 0.36 くらいなので 1 には遠いと考えるとニュアンス的にはそんな感じなのかなと思います

{
  "source": "大学生、、、元気だなw",
  "result": [
    {
      "target": "arduino ってまだまだ元気なのか、、なつかしい、、",
      "cos": 0.36860955705104625
    },
    {
      "target": "フィッツすっかり元気になっているじゃないか",
      "cos": 0.32779605613840673
    },
    {
      "target": "エシャロットって体に良いとかあるのかな。なんか食べた翌日は元気な気がする。たまたまかなw",
      "cos": 0.2422238044523076
    }
  ]
}

cos が 0.5 を超える類似度だと以下の通りです
確かに似ているような気がします、、、

{
  "source": "storyboard 落ちるわー、、、",
  "result": [
    {
      "target": "気持ち落ちるでぇ、、、",
      "cos": 0.5008608639512765
    },
    {
      "target": "なんか、storyboard もめっちゃ変わってるな",
      "cos": 0.40529191248135504
    },
    {
      "target": "立ち上がりはいつも落ちるなー。",
      "cos": 0.39170065104194474
    }
  ]
}

今回は 10 個しかサンプルリングしなかったので何とも言えませんが cos が 0.75 以上くらいで絞り込めばかなり似ているツイートを探せる印象です
ただ感じ的にはほとんどが 0.1 から 0.2 くらいだったので高スコアなペアを探せるのは結構レアなケースかなと思います

最後に

各ツイートごとに TFIDF を算出したのでコサイン類似度を使ってツイートのクラスタリングも行ってみました

この方法でも確かに似ているツイートは探せると思います
ただ今回の手法ではあくまでも「似ている」ツイートを探せているだけであり意味やコンテキストが似ているかどうかまでは判断できていません

例えばサンプリングの結果が見せた「storyboard 落ちるわー、、、」と「気持ち落ちるでぇ、、、」は確かにツイート的には似ていますがコンテキストとしては前者が Xcode に関するツイートで後者は体調や気持ちに関するツイートです
なので似てはいますが内容は全く違います

更に類似しているツイートを探したいのであれば今回クラスタリングした結果から更に TFIDF -> コサイン類似度を算出すれば良いと思います
TFIDF を算出した元データ (文書集合) がツイート全体だったので、文書集合をクラスタリング単位にすれば結果も変わってくると思います

そもそも Twitter 自体全く同じツイートはできないのはなかなか難しいですが、おもしろいアプローチかなと思います (定石かもしれませんが)

参考サイト

2019年2月16日土曜日

tfidf を学ぶ

概要

前回、自分のツイートデータを分析してみました
その際に単語の出現回数はカウントしましたが各ツイートに含まれる単語の tfidf は計算しませんでした
せっかくなので各ツイートの tfidf を算出して結果を考察してみました

環境

  • macOS 10.14.3
  • Ruby 2.5.1p57

tfidf を算出するスクリプト

とりあえずコードにしたので紹介します
勉強がてら作成してので参考程度に見てください

  • bundle init
  • vim Gemfile
gem "natto"
  • bundle install --path vendor
  • vim tfidf.rb
require 'natto'
require 'json'

class TFIDF
  attr_accessor :tweets

  def initialize
    file = 'data/tweet.js'
    @data = JSON.parse(File.read(file).sub('window.YTD.tweet.part0 = ', ''))
    @all_tweet_count = @data.size
    @tweets = []
  end

  def exec
    @data.each_with_index { |d, i|
      tweet = Tweet.new(d['full_text'])
      tweet.analyze
      add(tweet)
    }
    calc
  end

  def debug(all = false)
    ret = []
    @tweets.each { |tweet|
      r = {}
      rr = []
      if all
        tweet.terms.each { |term|
          rr.push(term.to_h)
        }
      else
        max = tweet.max_tfidf
        max.each { |term|
          rr.push(term.to_h)
        }
      end
      r.store(:tweet, tweet.tweet)
      r.store(:terms, rr)
      ret.push(r)
    }
    puts({ :results => ret }.to_json)
  end

  private
  def add(tweet)
    @tweets.push(tweet)
  end

  def calc
    @tweets.each { |t1|
      t1.terms.each { |term|
        c = 0
        @tweets.each { |t2|
          c += 1 if t2.tweet.include?(term.term)
        }
        term.df = c
        term.idf = Math.log(@all_tweet_count / c)
        term.tfidf = term.tf * term.idf
      }
    }
  end

  class Tweet
    attr_accessor :tweet, :terms

    def initialize(tweet)
      @tweet = tweet
      @terms = []
      @mecab = Natto::MeCab.new("-F%f[0]%f[1]")
    end

    def analyze
      @mecab.parse(@tweet) do |n|
        term = get(n.surface)
        if term.nil?
          term = Term.new(n.surface)
          add(term)
        else
          term.inc
        end
      end
      @terms.each { |t|
        t.tf = t.count / count_all_terms.to_f
      }
    end

    def max_tfidf
      max = @terms.max { |a, b| a.tfidf <=> b.tfidf }
      @terms.select { |t| t.tfidf == max.tfidf }
    end

    private
    def add(term)
      @terms.push(term)
    end

    def count_all_terms
      @terms.sum { |t| t.count }
    end

    def get(surface)
      @terms.select { |t| t.term == surface }.first
    end
  end

  class Term
    attr_accessor :term, :count, :tf, :df, :idf, :tfidf

    def initialize(term)
      @term = term
      @count = 1
      @tf = 0.0
      @df = 0
      @idf = 0.0
      @tfidf = 0.0
    end

    def inc
      @count += 1
    end

    def to_h
      {
        :term => @term,
        :count => @count,
        :tf => @tf,
        :df => @df,
        :idf => @idf,
        :tfidf => @tfidf
      }
    end
  end
end

tfidf = TFIDF.new
tfidf.exec
tfidf.debug

標準出力に JSON を出力するのでリダイレクトでファイルに書き込みましょう

  • bundle exec ruby tfidf.rb > results.json

今回は名詞だけに絞らず、すべての品詞を対象にしています
また解析対象のツイートは前回同様 10016 ツイートにします
つまり 10016 文書あるのと同じです

tfidf の値は特に正規化していません

結果は JSON で出力してくれます
すべての単語に対する tfidf を出力することもできますし文書内で最も tfidf が高かった単語だけを出力することもできます

tf, df, idf の計算式は定義通りです

  • tf・・・対象の単語 / 文書内の単語
  • df・・・対象の単語が全ツイートを対象に何ツイート出現したか
  • idf・・・Math.log(全ツイート数 / df)
  • tfidf・・・tf * idf

たぶん実装もあっていると思います

処理の流れ

まず各ツイートを対象に形態素解析を掛け単語を抽出します
各ツイート内で抽出した単語の出現回数をカウントします
抽出した単語とカウントは Term クラスの配列に追加していきます
出現回数と配列に追加した全単語を元に tf を計算します
これを全ツイート先に繰り返します

すべてツイートの単語の tf を算出できたら次はその単語が 1 回でも出現している文書 (ツイート) 数をカウントします (df)
df がカウントできたら idf を計算し、そのまま tfidf も計算します
計算結果は各単語を管理する Term クラスのフィールドとして管理します

あとは結果を JSON で出力して終了です

結果を見てみた

10,000 ツイート以上あるのですべてを見ていませんが適当にサンプリングして特徴語ってぽくなっているか見てみました

例えば以下のツイートは特徴語がちゃんと取得できているなと思いました

{
  "tweet": "Swift4 へのマイグレーションはできたけど Swift4 が勉強できたわけではない",
  "terms": [
    {
      "term": "Swift",
      "count": 2,
      "tf": 0.1,
      "df": 66,
      "idf": 5.017279836814924,
      "tfidf": 0.5017279836814924
    }
  ]
}

見るからに Swift に関数するツイートなので特徴語もバッチリだと思います
逆に特徴語が微妙な感じだったのは以下のようなツイートです

{
  "tweet": "lambda 使いになるには如何に layers を駆使できるかって感じがするなー。",
  "terms": [
    {
      "term": "駆使",
      "count": 1,
      "tf": 0.05,
      "df": 2,
      "idf": 8.518791912779934,
      "tfidf": 0.4259395956389967
    }
  ]
}

ツイートの雰囲気的には「lambda」とか「layers」が特徴語になってほしいところですが「駆使」になってしまいました
これは lambda や layers が他のツイートにも頻出しているせいで特徴語としての価値が下がっているせいで「駆使」が特徴語になっています

今回の場合、1 ツイートを文書として定義しました
Twitter の場合、連続して同じようなことをつぶやくケースが多く、そうなると単語の価値が下がる傾向があることがわかりました

なので 1 ツイート = 文書として扱うのではなく例えば 1 週間分のツイートを 1 文書として扱うことで 1 週間分のツイートでの特徴語を算出するようにすると良いのかなと思いました

あまり変な結果にはならなかった

全体をざっくり見ても変な結果になったツイートはありませんでした
例えば自分の場合「、、、」などツイートに多く含めるのですが「、、、」が特徴語になるようなケースは 1 度しか出ませんでした

他におかしそうなのはかぎかっこの記号が特徴語になるツイートもありましたがツイートを見ると確かにかぎかっこを多用していたので、それは正常かなと思いました

最後に

tfidf を使って自分のツイートの特徴語を抽出してみました
自然言語処理の分野になると思いますが、かなり基本的な手法になるので覚えておいて損はないかなと思います
またここから派生する手法としてクラスタリング手法であるコサイン類似度を使って同じような内容のツイートを発見したり、分類手法としてナイブベイズにも応用できると思います

自分もやってみて感じましたがやはり自分の手で動かして計算してみることで理解もより深まると思います

参考サイト

2019年2月9日土曜日

自分のツイートを分析してみた

概要

@hawksnowlog のツイートが 10,000 ツイートを達成したのでこれまでにどんなツイートをどれくらいしているのかなど分析してみました

環境

  • Ruby 2.5.1p57

事前準備: データをエクスポートする

「設定とプライバシー 」->「Twitter データ」からエクスポートできます

まずはリクエストします
データがダウンロード可能になると Twitter に登録しているメールアドレスに連絡が来るのでダウンロードします
データは JavaScript ファイルで提供されています
なので JavaScript を使えばすぐに import して使うことができます
が自分は Ruby を使いたかったので一旦 JSON ファイルに整形してから解析しました
また解析するデータは tweets.js のデータのみです

何を解析するか

いろいろ考えたのですがとりあえず以下を出してみたいと思います

  • 全ツイート数
  • 全ツイートをどれくらいの期間で行ったか
  • 月単位でどれくらいツイートを行ったか
  • 日単位でどれくらいツイートを行ったか
  • 月単位でどれくらいリツイートをされたか
  • 月単位でどれくらいファボをされたか
  • ツイートしているデバイスの割合
  • よくツイートされている単語 Top100

解析にあたって Ruby + natto + gruff を使っています
natto は形態素解析用として gruff はグラフの描画に使っています
以下では tweets.js の解析結果を紹介します

全ツイート数

解析対象となるツイート数です

  • 10016 ツイート

全ツイートをどれくらいの期間で行ったか

  • 2016/03/24 - 2019/02/07

で日数だと 1051 日になります
一日の平均ツイート数は 10016 ツイート / 1051 日 = 9.5 ツイート でした

月単位でどれくらいツイートを行ったか

結果のグラフです

月間最高ツイートは 2018/09 で 650 ツイートでした
月間最低ツイートは 2016/03 で 48 ツイートでした
また月平均だと 10016 ツイート/ 36 月= 278.2 ツイート でした

日単位でどれくらいツイートを行ったか

結果のグラフです

日で最高ツイートは 2018/06/05 で 88 ツイートでした
一日の平均は先程も記載しましたが 9.5 ツイートでした

月単位でどれくらいリツイートをされたか

結果のグラフです

最高でも月で 4 リツイートが一番多かったです
リツイートは 0 の月もざらにありました

月単位でどれくらいファボをされたか

結果のグラフです

リツイートよりもファボのほうがされやすいようです
月間最高で 15 最低は 1 でした
ただファボされなかった月はなかったようです

ツイートしているデバイスの割合

結果のグラフです

ほぼブラウザからツイートでした
あとはスマホ (Echofon) が少しだけあるようです

よくツイートされている単語 Top100

条件として MeCab の解析結果で「名詞」かつ「一般」の単語のみをカウントしています
なので実はもっとそれっぽい単語をツイートしている可能性もあります
この辺りの「有用な単語の探し方」は言語処理のプロではないので適当にやっています

  • t -> 926
  • co -> 914
  • https -> 844
  • アプリ -> 479
  • 感じ -> 395
  • 方法 -> 365
  • ー -> 342
  • 自分 -> 294
  • 人 -> 269
  • ファイル -> 214
  • インストール -> 210
  • docker -> 194
  • API -> 191
  • 情報 -> 167
  • 記事 -> 166
  • コンテナ -> 158
  • コマンド -> 157
  • gt -> 154
  • データ -> 154
  • コード -> 152
  • サーバ -> 152
  • ゲーム -> 143
  • アカウント -> 137
  • 久しぶり -> 134
  • あと -> 134
  • 自動 -> 125
  • 環境 -> 110
  • ruby -> 109
  • 画面 -> 102
  • ドメイン -> 100
  • 気 -> 98
  • ブログ -> 97
  • デフォルト -> 97
  • ビルド -> 97
  • Mac -> 94
  • ログイン -> 94
  • s -> 91
  • ページ -> 87
  • 別 -> 85
  • スタンプ -> 85
  • ユーザ -> 84
  • 個人 -> 83
  • mac -> 83
  • 複数 -> 80
  • ライブラリ -> 80
  • Google -> 79
  • サイト -> 78
  • ツール -> 77
  • UI -> 77
  • バージョン -> 77
  • iPhone -> 76
  • swift -> 75
  • p -> 73
  • web -> 73
  • iOS -> 73
  • ネットワーク -> 72
  • 動画 -> 72
  • 他 -> 71
  • or -> 69
  • 文字 -> 66
  • Arduino -> 65
  • 無料 -> 65
  • D -> 65
  • イベント -> 64
  • xcode -> 63
  • デプロイ -> 62
  • ローカル -> 61
  • mini -> 61
  • swagger -> 61
  • 画像 -> 60
  • 名前 -> 60
  • 時代 -> 59
  • 状態 -> 58
  • 変数 -> 58
  • google -> 58
  • 端末 -> 57
  • クラス -> 57
  • ケース -> 57
  • レベル -> 56
  • ドキュメント -> 55
  • 部分 -> 54
  • k -> 54
  • 月 -> 53
  • プロジェクト -> 53
  • golang -> 52
  • ホスト -> 51
  • タグ -> 51
  • クライアント -> 50
  • 次 -> 50
  • Swift -> 50
  • アップデート -> 50
  • メソッド -> 49
  • マイクラ -> 48
  • js -> 48
  • 使い方 -> 48
  • python -> 48
  • ポート -> 47
  • r -> 47
  • デバイス -> 47
  • ボタン -> 46

ちょっと考察してみます
まずトップ3 の t, co, https ですがこれは Twitter にリンクを張った場合、自動的に Twitter は短縮 URL に変換します
つまりツイートにリンクが含まれている内容が多いことを示しています
自分の場合ブログを書いたらそれをツイートしているのでそのせいかなと思います

342 回出現した「ー (伸ばし棒)」ですが確かにこれも納得かなと思います
ツイートの語尾に「かなー」とか確かに記載しているのでそのせいだと思います

「docker」「API」「アプリ」などエンジニアリングっぽい単語も結構出てきているようで、これは少し安心しました

154 回出ている「gt」ですがこれは自分が右矢印を記載する際に「->」と記載しています
そのせいで「>」が「gt」として解釈されているためです
ようするに右矢印を多用していることがわかります

意外だったのは「Mac」「iPhone」「Swift」などの Apple 関連の単語がそこまで多くなったことです
WWDC や Apple Special Event は毎回見ているし、デバイス情報も逐一追っているので結構出ているかなと思ったらそうでもありませんでした
もしかすると小文字は別の単語としてカウントしているので、それも合算するともっと上位に食い込むかもしれません

あとはところどころなぞの英小文字「s」「k」「r」などが入っています
これはよくわからなかったのですが、これらを含む英単語を多くツイートしているのかもしれません
具体的にどんな英単語かは少し思いつきませんでした
「w」だったら納得だったのですが、、

全体的に見るとまぁ納得という結果かなと思います

最後に

自分の Twitter データをエクスポートして分析してみました
やってみるとわかりますが自分だけではなくいろんなユーザの分析も同様にしてみるともっと面白いだろうなと感じました
ユーザの同士の動向からまたおもしろいデータがわかりそうな気がします

たぶんですがこういった研究をしている人はたくさんいると思うので論文を読めばわかりそうな気もしますが、やはり自分でやってみると楽しいものです