見出し画像

PythonでHRMOSから出力したCSVのデータを可視化するスクリプト

HRMOSのダッシュボードの数値では手の届かない痒い部分がある

前職でもHRMOSを使用していて、現在でもHRMOSを使用しています。
しかし、ダッシュボードでは自社に必要なデータを見ることが出来ないという理由でスプレッドシートに書き出して分析していました。

VLOOKUPを使って必要なデータを抽出したり、
複雑なCOUNTIFやらSUMIFを使って、ゴニョゴニョ….。
こういうのPythonで一発で出来たらいいよなぁーと思って作ったのが今回のスクリプトです。

手始めに今回作ったのは、採用媒体別の応募者数を棒グラフで出力するスクリプトです。手元にHRMOSのCSVデータがないため、グラフをお見せ出来ないのですが、ちょっとPythonの可能性を感じました。

実行環境

  • Windows 11

  • Visual Studio Code

Pythonスクリプト全文…..長いです。

長いけど、Chat GPTとかに書かせているので、自分ではそこまで書いていません。それよりもハルシネーション(AIがそれっぽい嘘を付くこと)が厄介です。プログラミングの場合はエラーが起きるので、今のが嘘だなと分かりやすいですが…..。

import os
from glob import glob
from datetime import datetime
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
import seaborn as sns

def get_latest_file(dir_path):
    """
    指定されたフォルダから最新のCSVファイルを取得する

    """
    files = glob(os.path.join(dir_path, "*.csv"))
    
    if not files:
        return None
    
    latest_file = max(files, key=os.path.getctime)
    print(f"最新のファイル名: {os.path.basename(latest_file)}\n\n")
    return latest_file



def select_df(df):
    """
    必要なカラムに限定して取得する 
    欠損値を '-' で埋める
    """

    df.fillna('-', inplace=True)
    print(df.columns)

    target_columns = ['応募ID', '求人ID', '選考ポジション名', '応募日', '氏名', '所属組織', '生年月日', '応募経路詳細', 'レジュメ(フリーテキスト)', '選考ステータス', '1次ステップ判定結果入力日', '2次ステップ判定結果入力日', '3次ステップ判定結果入力日', '4次ステップ判定結果入力日', '5次ステップ判定結果入力日', '不合格・重複終了日', '辞退理由(詳細)']
    df_selected = df[target_columns]

    return df_selected



def preprocess_data(df_selected):
    """
    データフレームの前処理を行います。

    Args:
        df_selected (pandas.DataFrame): 応募データが含まれるデータフレーム。
        media_counts (pandas.Series): メディアのカウント情報が含まれるシリーズ。

    Returns:
        pandas.DataFrame: 前処理後のデータフレーム。
    """

    # 株式会社など円グラフの表示に不要な部分を置換する
    df_selected['応募経路詳細'] = df_selected['応募経路詳細'].replace('-', '自社') 
    df_selected['応募経路詳細'] = df_selected['応募経路詳細'].apply(lambda x: x if x in ['ビズリーチ', 'Green', 'Wantedly', '自社', 'Findy'] else 'エージェント')

    # 応募経路詳細を置換した上で、媒体別の応募者数をカウント
    media_counts = df_selected['応募経路詳細'].value_counts().sort_values(ascending=False)

    # yyyy/MM/dd形式に変換後、データ型を日付時刻型に変換する
    df_selected['応募日'] = pd.to_datetime(df_selected['応募日'], format='%Y年%m月%d日').dt.strftime('%Y/%m/%d')
    df_selected['応募日'] = pd.to_datetime(df_selected['応募日'])
    print(df_selected['応募日'].dtype)

    monthly_applications = df_selected.groupby([df_selected['応募日'].dt.to_period('M'), '応募経路詳細']).size().unstack()
    monthly_applications = monthly_applications[media_counts.index]
    print(monthly_applications)

    return monthly_applications, media_counts



def select_color(media_counts):
    """
    積み上げ棒グラフの色を指定するための関数
    FIXME: color_mapに該当がない場合、灰色(#d0cacd)として出力されてしまう
    """
    color_map = {
        'ビズリーチ': '#0b2787',
        'Green': '#60b630',
        'Wantedly': '#21bddb',
        '自社': '#ff0000',
        'Findy': '#109f95'
    }
    colors = media_counts.index.map(lambda x: color_map.get(x, "#d0cacd")).tolist()
    print(colors)
    return colors



def set_style():
    """
    Seabornと日本語フォントの設定を行います。
    """
    sns.set_style('darkgrid', {'axes.grid': True, 'grid.color': '0.8'})
    sns.set_palette('winter_r')
    rcParams['font.family'] = 'sans-serif'
    rcParams['font.sans-serif'] = ['Yu Gothic', 'Meirio']



def add_percentage_labels(ax, monthly_applications):

    """
    積み立て棒グラフに各バーの割合を%で表示します。
    
    Axesオブジェクトとは、matplotlibにおける中心的なクラスの一つです。
    グラフの描画領域(プロットエリア)を定義します。
    グラフのタイトル、軸のラベル、軸の範囲など、グラフの各要素を管理します

    Args:
        ax (matplotlib.axes.Axes): 描画する棒グラフのAxesオブジェクト。
        monthly_applications (pandas.DataFrame): 月別の応募数が含まれるデータフレーム。

    Returns:
        None
    """

    for p in ax.patches:
        width, height = p.get_width(), p.get_height()

        # バーの高さが0でない場合のみテキストを描画
        if height > 0:
            x, y = p.get_xy()

            # 各バーの割合を計算して表示
            percentage = height / monthly_applications.sum(axis=1).iloc[int(x + width / 2)] * 100
            ax.text(x + width / 2,
                    y + height / 2,
                    '{:.0f}%'.format(round(percentage)),  # テキストを整数で表示
                    horizontalalignment='center',
                    verticalalignment='center',
                    color='white',
                    fontdict={'weight': 'bold'})



def fill_missing_values(data):
    """
    欠損値を0で置き換え、小数点以下を切り捨てる
    """
    data = data.copy()
    data = data.fillna(0).astype(int)
    print(data.head(3))
    return data



def create_table(ax, df):
    """
    指定された軸上にデータフレームを表形式で表示します。

    Args:
        ax (matplotlib.axes.Axes): 表を表示する軸。
        df (pandas.DataFrame): 表示するデータフレーム。

    Returns:
        matplotlib.table.Table: 作成した表。
    """
    ax.axis('off')
    table = ax.table(cellText=df.values,
                     colLabels=df.columns,
                     rowLabels=df.index,
                     cellLoc='center', 
                     loc='center',
                     bbox=[0, 0.3, 1, 1.4])

    table.auto_set_font_size(False)
    table.set_fontsize(12)
    table.scale(1.1, 1.3)  # スケールの調整

    return table



def decorate_table(table):
    """
    表に色分けや装飾を追加します。

    Args:
        table (matplotlib.table.Table): 装飾を追加する表。
    """
    # ヘッダー行の色を変更
    header_color = '#40466e'  # ヘッダー行の背景色を指定

    for key, cell in table.get_celld().items():
        if key[0] == 0:
            cell.set_text_props(weight='bold', color='white')  # テキストの色を白に変更
            cell.set_facecolor(header_color)  # 背景色を再設定

        elif key[0] % 2 == 0:

            # visible_edgeの記載があると、set_facecolorが無効化されるかもしれない
            cell.visible_edges = 'BT'
            # cell.set_facecolor('#d8dae2')

        else:

            # 縦の罫線を消す
            cell.visible_edges = 'BT'  # 下端と上端の罫線のみ表示

        # 罫線の色を変更
        cell.set_edgecolor('#797d99')



def create_bar_chart(monthly_applications, colors):
    
    """
    月別の応募者数を積み立て棒グラフで描画し、画像として保存します。

    Args:
        df_selected (pandas.DataFrame): 応募データが含まれるデータフレーム。
        media_counts (pandas.Series): メディアのカウント情報が含まれるシリーズ。
        colors (list): 棒グラフの色を指定するリスト。
        destination_path (str): グラフの画像を保存するパス。

    Returns:
        None
    """

    set_style()
    fig, ax = plt.subplots(figsize=(12, 8))

    # 棒グラフを描画
    monthly_applications.plot(kind='bar', stacked=True, ax=ax, color=colors, edgecolor="none")

    # X軸のラベルを回転
    plt.xticks(rotation=0)

    # 縦の罫線を消す
    plt.rcParams['axes.grid.axis'] = 'y'
    ax.xaxis.grid(False)

    plt.title('媒体別応募数の推移', fontsize=20, fontdict={'weight': 'bold'})
    plt.xlabel('')  # 何も書かないと応募日が表示されてしまう

    # 凡例の表示(背景色、罫線無し、グラフの外)
    plt.legend(bbox_to_anchor=(1.0, 1), loc='upper left', fontsize=14, facecolor='none', frameon=False)
    plt.tight_layout()

    # 各バーの割合を%で表示
    add_percentage_labels(ax, monthly_applications)  # 別途定義されている関数

    return fig



def create_chart_and_table(monthly_applications, colors):
    
    """
    月別の応募者数を積み立て棒グラフで描画し、画像として保存します。

    Args:
        df_selected (pandas.DataFrame): 応募データが含まれるデータフレーム。
        media_counts (pandas.Series): メディアのカウント情報が含まれるシリーズ。
        colors (list): 棒グラフの色を指定するリスト。
        destination_path (str): グラフの画像を保存するパス。

    Returns:
        None
    """

    set_style()

    fig = plt.figure(figsize=(12, 9))
    ax1 = fig.add_subplot(211) # 上のグラフ
    ax2 = fig.add_axes([0.1, 0.065, 0.6, 0.2]) # 下の表を描画する場所の指定

    """

    最初の値 (x): 表の左下隅の x 座標 (0.0 はグラフの左端、1.0 はグラフの右端)
    2番目の値 (y): 表の左下隅の y 座標 (0.0 はグラフの下端、1.0 はグラフの上端)
    3番目の値 (width): 表の幅 (0.0 から 1.0 まで)
    4番目の値 (height): 表の高さ (0.0 から 1.0 まで)

    """
    
    # 棒グラフを描画
    monthly_applications.plot(kind='bar', stacked=True, ax=ax1, color=colors, edgecolor="none")

    # X軸のラベルを回転
    ax1.tick_params(axis='x', labelrotation=0)  # ax1 に対して回転を設定

    # 縦の罫線を消す
    plt.rcParams['axes.grid.axis'] = 'y'
    ax1.xaxis.grid(False)  # ax1 に対してグリッドを設定

    ax1.set_title('媒体別応募数の推移', fontsize=20, fontdict={'weight': 'bold'})  # ax1 に対してタイトルを設定
    ax1.set_xlabel('')  # ax1 に対して X 軸ラベルを設定

    # 凡例の表示(背景色、罫線無し、グラフの外)
    ax1.legend(bbox_to_anchor=(1.0, 1), loc='upper left', fontsize=14, facecolor='none', frameon=False)
    plt.tight_layout()

    # 各バーの割合を%で表示
    add_percentage_labels(ax1, monthly_applications)  # 別途定義されている関数
    
    # グラフと一緒にテーブルを描画
    data = fill_missing_values(monthly_applications)
    table = create_table(ax2, data)
    decorate_table(table)

    return fig



def get_current_time_string():
    """
    現在の日時を文字列形式で取得します。

    Returns:
        str: 現在の日時を表す文字列(形式: "YYYY_MMDD_HHMM")。
    """
    current_time = datetime.now()
    date_string = current_time.strftime("%Y_%m%d_%H%M")
    return date_string



def save_fig(fig, destination_path, file_name):
    """
    グラフを画像として保存します。

    Args:
        fig (matplotlib.figure.Figure): 保存するグラフのFigureオブジェクト。
        destination_path (str): グラフの画像を保存するパス。
    """
    merged = f'{destination_path}{file_name}'
    fig.savefig(merged)
    plt.show()  # 必要であれば表示


def visualize_monthly_application():
    """
    HRMOSのCSVから月間の応募者数を採用媒体別に集計、グラフで表示する関数
    半期ベースで見る事を想定
    FIXME: 対象期間が長いと表やグラフが見切れる可能性がある
    
    """
    # hrmos_csvのフォルダから最新のCSVを取得する
    dir_path = r'c:/Users/user/Desktop/*********/hrmos_csv/'
    latest_file = get_latest_file(dir_path)
    if os.path.exists(latest_file):
        print("ファイルが存在します")

        # HRMOSのCSVをUTF-16形式でData Frameとして取得する
        df = pd.read_csv(latest_file, encoding='utf-16', sep='\t')
        df_selected = select_df(df)

        # 月間の媒体別応募者数を算出
        monthly_applications, media_counts = preprocess_data(df_selected)
        colors = select_color(media_counts)

        # 日本語表記でグラフを描画
        set_style()
        # fig = create_bar_chart(monthly_applications, colors)
        fig = create_chart_and_table(monthly_applications, colors)

        # グラフを保存するフォルダとファイル名を指定
        date_string  = get_current_time_string()
        destination_path = r'c:/Users/user/Desktop/************/'
        file_name = f'bar_graph_{date_string}.jpg'

        # 描画されたグラフを保存する
        save_fig(fig, destination_path, file_name)

    else:
        print("ファイルが存在しません")


if __name__ == "__main__":
    visualize_monthly_application()


この記事が気に入ったらサポートをしてみませんか?