作ったもの
イーロンマスクのツイートにおける頻出単語の時系列変化を可視化したgif画像です。
pip install AnimatedWordCloudTimelapse
でインストールして、
from AnimatedWordCloud import animate
animate(data)
と記述するだけで、上記のgif画像が作成できるPython
ライブラリAnimatedWordCloudを作成しました。
GitHub
レポジトリ(スター⭐くれると泣きながら喜びます、本当に): https://github.com/konbraphat51/AnimatedWordCloud
はじめに
テキストマイニング を行っていると、対象が何を話題にしているのか 時系列変化 を知りたい時、ありますよね。
まず、静的に(「時系列変化」ではなく)、とりあえず何が話題になっているのか見せたいという時は、 ワードクラウド が使われます。
ワードクラウドといえば、自分の学科に鳥海不二夫さんという、ツイッター(旧X)で何か炎上ごとが発生するたびにYahoo記事にワードクラウドを投稿する教授がいらっしゃいますが、例えばこの記事のように、話題が可視化されています。
(上記の鳥海さんの記事「日本対スペイン戦で何がツイートされたか」より引用)
このように、コンパクトに、素人にも見た目で話題が分かるような画像になっているのが特徴です。
ここで、まさにこの記事のように、 時系列での 話題変化を アニメーション化 できれば、
- さらにコンパクトになる
(複数の画像を使わずに、一つの動画を見れば良い) - 何が新しく話題になり、何が残留し、何が話題にならなくなったのか確認することができる
- なんか動いているから良い仕事している感じが出る
という点で嬉しいのではないでしょうか。
なので、
この記事は
-
AnimatedWordCloud
の宣伝をし - OSS開発作業の一例として参考になるように記述し、
- ちょっとだけアルゴリズムに触れます
使用例
イーロンマスクのツイート
データセットはdata.worldのAdam Helsinger氏のものをお借りしています
こちらのJupyter NotebookをColabに読み込ませれば使用できます
データを作る
-
単語ベクトル を
単語->重み
のdictionary
を作り
#年ごとに
for cnt in range(len(dfs_by_year)):
#ツイートごとのCountベクトルのデータフレーム
np_year = counter.fit_transform(dfs_by_year[cnt]["text"]).toarray()
df_year = pd.DataFrame(np_year, columns=counter.get_feature_names_out())
#その年の総和
sr_sum = df_year.sum(axis=0).sort_values(ascending=False)
#10回以上のもののみ抽出
sr_sum = sr_sum[sr_sum >= 10]
#dictionary型へ
dict_year = sr_sum.to_dict()
# 年代別リストに保存
dicts_by_year.append(dict_year)
-
(タイムスタンプ名, 単語ベクトルdictionary)
のタプルの リスト にします
wordvector_timelapse = []
# 年ごとに
for cnt in range(len(dicts_by_year)):
year = str(2012 + cnt) #タイムスタンプ名(年を表す)
wordvector_timelapse.append((year, dicts_by_year[cnt])) #タプルにしてリストに追加
アニメーション化
データを作れたら、これで アニメーションgif画像を作れます
from AnimatedWordCloud import animate, Config
config = Config(output_path="/content/") #出力先のディレクトリを指定
result = animate(wordvector_timelapse, config) #アニメーション化
デフォルトだと、output.gif
としてgifが出てきます(Config
クラスで設定できます)
ちなみに、 gif画像をJupyter Notebook上に表示するには
from IPython.display import display, Image
with open('/content/output.gif','rb') as f:
display(Image(data=f.read(), format='png'))
こう書きます。
ニコ動人気タグ
日本語を用いる場合、fontの設定 が必要です。
共同開発者が書いてくれています:
ニコ動の人気タグ遷移を一発でアニメーション化できるpythonライブラリが生まれたらしいので試してみた
英誌Guardianタグ
クリックすると画質がいいMediumの記事へ飛びます。
Animate the timelapse of 2023
開発体制
作業フロー
設計
責務を切り分け、一つの責務ごとにモジュールを一つ割り当てます。
頑張ってSOLIDします。
作図にはPlantUML
を使用しています。
分担実装
モジュールごとに ブランチを切ってプルリクでマージしていきます。
ちゃんとSOLID設計しているので、他のモジュールが未完でも、自分のモジュールのI/O
を全うすることだけに集中すればいいので同時並行できるというわけですね。
テスト
モジュールごとに単体テストコードを書き、GitHub Actions
で確認してからモジュールを実装しましたが、これは機械的なテストだとビジュアルを確認できないので、手動で確認しつつデバッグを実施しました。
下記GitHub Action
を書くと、push
するたびに
- バージョン3.8~3.12の5バージョンで、
- MacOS, Windows, Ubuntuの3OSの
3*5=15
通りの仮想環境が立ち上げられテストされます。public
レポジトリなら無料です。
GitHub
さんは懐深いの王、懐深いキングですね。
name: unit-test
on:
push:
branches: ["dev", "main"]
pull_request:
branches: ["dev", "main"]
jobs:
build:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
platform: ["ubuntu-latest", "macos-latest", "windows-latest"]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install pytest
python -m pip install -e .
- name: Test with pytest
env:
PYTHONPATH: ${{ github.workspace }}
run: |
pytest
ドキュメンテーション
を書きました。
リリース
作業手順
平時はdev
ブランチで開発していますが、
- 最終成果物をバージョンアップのたびに
main
ブランチにマージし、 -
GitHub
のreleases
を更新する度にPyPI
アップロードを自動化するGitHub Actionを作成しました。
name: Upload Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.12"
- name: Build package
run: |
python -m pip install --upgrade pip
pip install setuptools wheel
python setup.py sdist bdist_wheel
- name: Publish package
run: |
pip install twine
python -m twine upload dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
設定
を記述します。
保守性維持
承認制
コード理解と保守性のため、しっかりコードレビューで承認されてからプルリクをマージします
他にも、機械的に、GitHub Actionsで
-
Linter
コーディング規約への適合を確認する -
CodeClimate
外面的な綺麗さを評価してくれます -
Codacy
内面的な綺麗さを評価してくれます。 -
pytester
push
するたびにテストコードが走ります。
を通じて保守性チェックをしています。
(参考: 【GitHub】レビュワーがいなくとも綺麗にコード書く気になるサービス2選と利用方法)
コーディング
Black
を使ってコーディング規約順守を自動化しています。
参考: blackで始める妥協のないPythonコードフォーマッティング
簡単にいうと、
pip install black
でインストールし、カレントディレクトリをレポジトリ直下に移して
python -m black --line-length=79 .
とコマンドするだけで、 自動整形してくれます
Black
の 使用を怠っていないか の確認は、下記GitHub Action
で行えます。
name: Lint
on:
push:
branches:
- dev
paths-ignore:
- '**.md'
- '**.yml'
- 'docs/**'
pull_request:
branches:
- dev
paths-ignore:
- '**.md'
- '**.yml'
- 'docs/**'
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Check Code Format with Black
uses: psf/black@stable
with:
options: "--check --verbose --line-length=79"
src: "./AnimatedWordCloud"
version: "~= 22.0"
アーキテクチャ
下記の通りに。
【Python】モジュール構成規則を考えてみる
中身の仕組みちょっとだけ
概要
- 与えられた時系列データをライブラリ内部で定義しているクラス構造に変換し
-
StaticAllocationCalculator
: 時系列のタイムスタンプごとにワードクラウドの 位置決め を行い(普通にワードクラウドを作成) -
AnimatedAllocationCalculator
: 上記の静的な位置決めの 合間の コマとなるデータを作成し、(これでヌルヌル動くように見える) -
ImageCreator
: 各位置決めデータから画像を作り -
AnimationIntegrator
: 画像を統合し一つのgif
画像にします。
モジュール構造
Root
まず、大きく2つ、
-
Animator
モジュールと - 共通処理が書かれた
Utils
モジュール
に分けて用意しています。
(X -> Y
は「XがYに依存している」ことを示す)
Animatorモジュール
上記のフロー図通り配置しています。
Animator.py
が各モジュールを拾いに行き、それ以外のモジュール間依存はありません。
Utilsモジュール
共通処理・クラスを書いているだけです。
StaticAllocationCalculator
先ほど言った「まずは普通にワードクラウドを作ってみる」の部分です。
僕が書きましたが、これメンドウですね。
アルゴリズムとしては、
- まずは最頻の単語を真ん中に設置し、
- 次に頻度が高い単語から順に 磁石のように 真ん中のクラスタへひっつけていく
ようにしています。
これはその磁石ひっつけのアニメーションです。
(映像は開発初期のものであり、処理速度・綺麗さ共にリリースしているバージョンは格段に品質が良いです)
ベクトル計算、当たり判定であったり日頃のゲームプログラミング経験が活きましたね。
処理としては、
- 上下左右から真ん中の磁石クラスタの輪郭の点群(二次元)を取得し
- 得られた上記の点群のうち、どれに次の単語を設置するのがよいか、評価関数が最良の値を示す点を走査する
というものです。
joblib
ライブラリで並列処理化し、高速化しています。
最後に
僕はGitHub
のレポジトリの数自体は多いのですが、ちょっと規模のあるものをOSSとして真面目にリリースまでもっていくのは初めてでした。
モジュールを結合していき、初めて動いたワードクラウドを出せた時、中学1年生で初めてゲームを作ったときと似た感動と快感が生まれました。
やっぱりプログラミングは楽しいですね。
- この記事にいいね
- GitHubレポジトリにスター⭐
つけていただけると本当に嬉しいです。
初めてのリリースなので、お願いします。