見出し画像

Python matplotlibで描画したグラフ画像をSlackで送るスクリプト(2024年10月投稿 files_upload_v2を使用)

採用データの可視化に取り組んでいて、matplotlibで描画したグラフをSlackで送りたいなと思う場面がありました。
その備忘録として残しておきます。なお、files_upload_v2で書いています。
今回もGoogle Colabを使用しています。


スクリプト実行時

スクリプト実行時のイメージ

必要なBot Token Scopesについて

  • chat:write(メッセージ送信に必要なScope)

  • files:write(ファイルをアップロードに必要なScope)

サンプルスクリプト

import matplotlib.pyplot as plt
import japanize_matplotlib
import io
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

# Google Colabの認証を付与する
from google.colab import userdata
from google.colab import auth
auth.authenticate_user()

# Google Spreadsheetとの連携
import gspread
from google.auth import default
creds, _ = default()
gc = gspread.authorize(creds)



def create_bar_chart():
  # 描画に必要なデータの準備
  week_numbers = [35, 36, 37, 38, 39, 40, 41, 42]
  scout_counts = [50, 75, 35, 42, 34, 82, 54, 26]
  reply_rates  = [4, 9, 7, 5, 4, 6, 7, 4]

  print(f"week_numbers: {week_numbers} len: {len(week_numbers)}")
  print(f"scout_counts: {scout_counts} len: {len(scout_counts)}")
  print(f"reply_rates: {reply_rates}  len: {len(reply_rates)}\n\n")

  # プロットの設定
  fig, ax1 = plt.subplots()

  # 棒グラフのプロット
  bars = ax1.bar(week_numbers, scout_counts, color='#60c4a8', label='スカウト送付数')
  ax1.set_xlabel('月')
  ax1.set_ylabel('スカウト送付数')

  # 棒グラフに数値を追加
  for bar, count in zip(bars, scout_counts):
      height = bar.get_height()
      ax1.text(bar.get_x() + bar.get_width() / 2, height, count,
              ha ='center', va ='bottom')

  # 折れ線グラフのプロット
  ax2 = ax1.twinx()
  ax2.set_ylabel('返信率 (%)', color='r')
  ax2.plot(week_numbers, reply_rates, color='r', marker='s', label='返信率', markersize=8, linewidth=2)

  # グラフのタイトルと凡例の設定
  plt.title('スカウト送付率と全職種の返信率')
  fig.tight_layout()
  plt.show()

  return fig


def upload_plot_to_slack(channel_id, fig):
    # プロットをPNG形式でバッファに保存
    buf = io.BytesIO()
    fig.savefig(buf, format='png')
    buf.seek(0)

    # Slackクライアントを初期化
    client = WebClient(token=userdata.get('SLACK_BOT_USER_TOKEN'))

    try:
        # Slackにファイルをアップロード
        response = client.files_upload_v2(
            channel = channel_id,
            file = buf.getvalue(),
            title = '週次スカウト結果のグラフ',
            initial_comment = "週次スカウト結果の共有です。直近2か月間を集計対象としています。"
        )
        permalink = response['file']['permalink']
        print(f"File uploaded successfully: {permalink}")
        return permalink

    except SlackApiError as e:
        print(f"Error uploading file: {e.response['error']}")
        return None

    finally:
        buf.close()


def main():
  channel_id = userdata.get('SLACK_CHANNEL_ID')
  fig = create_bar_chart()
  upload_plot_to_slack(channel_id, fig)


if __name__ == "__main__":
  main()

注意点

ライブラリのインストールについて

%%bash
pip install -q japanize-matplotlib

channel_idについて

SLACK_CHANNEL_IDは、channel_idのみ有効です。
テストでDMに送ろうと何度も試したのですが、メンバーID(user_id)の場合はエラーになってしまい成功しませんでした。

Bot User OAuth Tokenの場合

Bot User OAuth Tokenの場合は、チャンネルにappを追加する必要があります。
Integrations > Add appsをクリックして、グラフを送るappを追加してください。
app追加はチャンネルのオーナーのみ可能です。

メンバーを招待するのと同じ要領で追加可能

userdata.get('*******')

Google Colabオリジナルの環境変数です。os.getenvみたいな感じです。
鍵のアイコンから登録できます。

複合グラフを作る際の注意点

このスクリプトの大半はAIに書いてもらったものですが、複合グラフを作る際は、barを使用してください。

棒グラフと折れ線グラフ両方が正しく表示されるケース

bars = ax1.bar(week_numbers, scout_counts, color='#60c4a8', label='スカウト送付数')

棒グラフのみしか表示されないケース

pivot_data = df.pivot_table(index='週番号', columns=['曜日', '職種', '返信の有無'], values='count', fill_value=0)

pivot_tableを使って棒グラフを描写することも出来るようですが、使用すると折れ線グラフが表示されなくなりました。理由はよく分かりませんが、barを使う方が目的を早く達成することができます。

参考にしたGitHubについて

# 単一ファイルを送る方法
response = client.files_upload_v2(
    file="./logo.png",
    title="New company logo",
    # Note that channels still works but going with channel="C12345" is recommended
    # channels=["C111", "C222"] is no longer supported. In this case, an exception can be thrown 
    channels=["C12345"],
    initial_comment="Here is the latest version of our new company logo :wave:",
)
response.get("file")  # returns the full metadata of the uploaded file

# 複数ファイルを送る方法
response = client.files_upload_v2(
    file_uploads=[
        {
            "file": "./logo.png",
            "title": "New company logo",
        },
        {
            "content": "Minutes ....",
            "filename": "team-meeting-minutes-2022-03-01.md",
            "title": "Team meeting minutes (2022-03-01)",
        },
    ],
    channel="C12345",
    initial_comment="Here is the latest version of our new company logo :wave:",
)
response.get("files")  # returns the full metadata of all the uploaded files

複数ファイルを送る方法について、今回は検証していません。

追記:Spreadsheetから情報を読み取るスクリプト

def read_spreadsheet():

    """
    採用数値のRawデータを取得しデータフレームを返します

    Returns:
        DataFrame: アクティブなユーザーのデータフレーム
    """

    sheet_url = "https://docs.google.com/spreadsheets/d/***********"
    spreadsheet = gc.open_by_url(sheet_url)
    sheet = spreadsheet.worksheet('シート名を入れる')
    original_data = sheet.get_all_records()

    df = pd.DataFrame(original_data)
    return df

積み上げ棒グラフの描画について

多分、採用数値の可視化に興味を示す人であれば、職種別に、スカウト媒体別に積み上げ棒グラフで描画できないかと思うはず。
関連記事を書いているので、併せて覗いてみてください。

媒体別の送信数、返信数を示したグラフ


いいなと思ったら応援しよう!