LoginSignup
40
52
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

【Python OSSライブラリ製作】話題の時系列変化を可視化する"アニメ化"ワードクラウドを作成するライブラリをリリースした

Last updated at Posted at 2024-01-08

作ったもの

イーロンマスクのツイートにおける頻出単語の時系列変化を可視化したgif画像です。
image.gif

pip install AnimatedWordCloudTimelapse

でインストールして、

from AnimatedWordCloud import animate

animate(data)

と記述するだけで、上記のgif画像が作成できるPythonライブラリAnimatedWordCloudを作成しました。

GitHubレポジトリ(スター⭐くれると泣きながら喜びます、本当に): https://github.com/konbraphat51/AnimatedWordCloud

はじめに

テキストマイニング を行っていると、対象が何を話題にしているのか 時系列変化 を知りたい時、ありますよね。

まず、静的に(「時系列変化」ではなく)、とりあえず何が話題になっているのか見せたいという時は、 ワードクラウド が使われます。

ワードクラウドといえば、自分の学科鳥海不二夫さんという、ツイッター(旧X)で何か炎上ごとが発生するたびにYahoo記事にワードクラウドを投稿する教授がいらっしゃいますが、例えばこの記事のように、話題が可視化されています。
IMG_1063.jpeg
(上記の鳥海さんの記事「日本対スペイン戦で何がツイートされたか」より引用)

このように、コンパクトに、素人にも見た目で話題が分かるような画像になっているのが特徴です。

ここで、まさにこの記事のように、 時系列での 話題変化を アニメーション化 できれば、

  • さらにコンパクトになる
    (複数の画像を使わずに、一つの動画を見れば良い)
  • 何が新しく話題になり、何が残留し、何が話題にならなくなったのか確認することができる
  • なんか動いているから良い仕事している感じが出る

という点で嬉しいのではないでしょうか。

なので、

この記事は

  • AnimatedWordCloudの宣伝をし
  • OSS開発作業の一例として参考になるように記述し、
  • ちょっとだけアルゴリズムに触れます

使用例

イーロンマスクのツイート

冒頭に紹介したものですね。
image.gif

データセットは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上に表示するには

gif表示.py
from IPython.display import display, Image

with open('/content/output.gif','rb') as f:
    display(Image(data=f.read(), format='png'))

こう書きます。


ニコ動人気タグ

日本語を用いる場合、fontの設定 が必要です。

共同開発者が書いてくれています:
ニコ動の人気タグ遷移を一発でアニメーション化できるpythonライブラリが生まれたらしいので試してみた
nico


英誌Guardianタグ

クリックすると画質がいいMediumの記事へ飛びます。
ezgif-3-b757a4ba2e.gif
Animate the timelapse of 2023

開発体制

作業フロー

設計

責務を切り分け、一つの責務ごとにモジュールを一つ割り当てます。
頑張ってSOLIDします。

作図にはPlantUMLを使用しています。

分担実装

モジュールごとに ブランチを切ってプルリクでマージしていきます。
image.png

ちゃんとSOLID設計しているので、他のモジュールが未完でも、自分のモジュールのI/Oを全うすることだけに集中すればいいので同時並行できるというわけですね。

テスト

モジュールごとに単体テストコードを書き、GitHub Actionsで確認してからモジュールを実装しましたが、これは機械的なテストだとビジュアルを確認できないので、手動で確認しつつデバッグを実施しました。
image.png

下記GitHub Actionを書くと、pushするたびに

  • バージョン3.8~3.12の5バージョンで、
  • MacOS, Windows, Ubuntuの3OSの

3*5=15通りの仮想環境が立ち上げられテストされます。publicレポジトリなら無料です。
GitHubさんは懐深いの王、懐深いキングですね。

自動テスト.yml
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ブランチにマージし、
  • GitHubreleasesを更新する度にPyPIアップロードを自動化するGitHub Actionを作成しました。
PyPI上げAction.yml
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 }}
設定

を記述します。

保守性維持

承認制

コード理解と保守性のため、しっかりコードレビューで承認されてからプルリクをマージします
image.png

他にも、機械的に、GitHub Actionsで

を通じて保守性チェックをしています。
(参考: 【GitHub】レビュワーがいなくとも綺麗にコード書く気になるサービス2選と利用方法

コーディング

Blackを使ってコーディング規約順守を自動化しています。
参考: blackで始める妥協のないPythonコードフォーマッティング

簡単にいうと、

pip install black

でインストールし、カレントディレクトリをレポジトリ直下に移して

python -m black --line-length=79 .

とコマンドするだけで、 自動整形してくれます

Black使用を怠っていないか の確認は、下記GitHub Actionで行えます。

black使用確認.yml
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が各モジュールを拾いに行き、それ以外のモジュール間依存はありません。
image.png

Utilsモジュール

共通処理・クラスを書いているだけです。


StaticAllocationCalculator

先ほど言った「まずは普通にワードクラウドを作ってみる」の部分です。
僕が書きましたが、これメンドウですね。

アルゴリズムとしては、

  • まずは最頻の単語を真ん中に設置し、
  • 次に頻度が高い単語から順に 磁石のように 真ん中のクラスタへひっつけていく

ようにしています。

これはその磁石ひっつけのアニメーションです。
image
(映像は開発初期のものであり、処理速度・綺麗さ共にリリースしているバージョンは格段に品質が良いです)

ベクトル計算、当たり判定であったり日頃のゲームプログラミング経験が活きましたね。

処理としては、

  • 上下左右から真ん中の磁石クラスタの輪郭の点群(二次元)を取得し
  • 得られた上記の点群のうち、どれに次の単語を設置するのがよいか、評価関数が最良の値を示す点を走査する

というものです。

joblibライブラリで並列処理化し、高速化しています。

最後に

僕はGitHubのレポジトリの数自体は多いのですが、ちょっと規模のあるものをOSSとして真面目にリリースまでもっていくのは初めてでした。

モジュールを結合していき、初めて動いたワードクラウドを出せた時、中学1年生で初めてゲームを作ったときと似た感動と快感が生まれました。
やっぱりプログラミングは楽しいですね。

つけていただけると本当に嬉しいです。
初めてのリリースなので、お願いします。

40
52
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
52