棒グラフ#

準備#

Import#

Hide code cell content
# warningsモジュールのインポート
import warnings

# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Hide code cell content
# itertoolsモジュールのインポート
# 効率的なループを実行するためのイテレータビルディングブロックを提供
# これにより、データのコンビネーションや順列などを簡潔に表現できる
import itertools

# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
from pathlib import Path

# numpy:数値計算ライブラリのインポート
# npという名前で参照可能
import numpy as np

# pandas:データ解析ライブラリのインポート
# pdという名前で参照可能
import pandas as pd

# plotly.expressのインポート
# インタラクティブなグラフ作成のライブラリ
# pxという名前で参照可能
import plotly.express as px

# plotly.graph_objectsからFigureクラスのインポート
# 型ヒントの利用を主目的とする
from plotly.graph_objects import Figure

変数#

Hide code cell content
# マンガデータ保存ディレクトリのパス
DIR_CM = Path("../../../data/cm/input")
# アニメデータ保存ディレクトリのパス
DIR_AN = Path("../../../data/an/input")
# ゲームデータ保存ディレクトリのパス
DIR_GM = Path("../../../data/gm/input")

# マンガデータの分析結果の出力先ディレクトリのパス
DIR_OUT_CM = (
    DIR_CM.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "bar"
)
# アニメデータの分析結果の出力先ディレクトリのパス
DIR_OUT_AN = (
    DIR_AN.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "bar"
)
# ゲームデータの分析結果の出力先ディレクトリのパス
DIR_OUT_GM = (
    DIR_GM.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "bar"
)
Hide code cell content
# 読み込み対象ファイル名の定義

# マンガ作品とマンガ作者の対応関係に関するファイル
FN_CC_CRT = "cm_cc_crt.csv"

# マンガ各話に関するファイル
FN_CE = "cm_ce.csv"

# アニメ作品と原作者の対応関係に関するファイル
FN_AC_ACT = "an_ac_act.csv"

# アニメ各話に関するファイル
FN_AE = "an_ae.csv"

# ゲームパッケージとプラットフォームの対応関係に関するファイル
FN_PKG_PF = "gm_pkg_pf.csv"
Hide code cell content
# plotlyの描画設定の定義

# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"
Hide code cell content
# 可視化に関する設定値の定義

# 「年代」の集計単位
UNIT_YEARS = 10
Hide code cell content
# pandasのweekday関数で取得できる曜日の数値と実際の曜日名を対応させる辞書を定義
# 0:月曜日, 1:火曜日, ... , 6:日曜日
WEEKDAY2YOBI = {
    0: "月",
    1: "火",
    2: "水",
    3: "木",
    4: "金",
    5: "土",
    6: "日",
}
Hide code cell content
# 質的変数の描画用のカラースケールの定義

# Okabe and Ito (2008)基準のカラーパレット
# 色の識別性が高く、多様な色覚の人々にも見やすい色組み合わせ
# 参考URL: https://jfly.uni-koeln.de/color/#pallet
OKABE_ITO = [
    "#000000",  # 黒 (Black)
    "#E69F00",  # 橙 (Orange)
    "#56B4E9",  # 薄青 (Sky Blue)
    "#009E73",  # 青緑 (Bluish Green)
    "#F0E442",  # 黄色 (Yellow)
    "#0072B2",  # 青 (Blue)
    "#D55E00",  # 赤紫 (Vermilion)
    "#CC79A7",  # 紫 (Reddish Purple)
]

関数#

Hide code cell source
def show_fig(fig: Figure) -> None:
    """
    所定のレンダラーを用いてplotlyの図を表示
    Jupyter Bookなどの環境での正確な表示を目的とする

    Parameters
    ----------
    fig : Figure
        表示対象のplotly図

    Returns
    -------
    None
    """

    # 図の周囲の余白を設定
    # t: 上余白
    # l: 左余白
    # r: 右余白
    # b: 下余白
    fig.update_layout(margin=dict(t=25, l=25, r=25, b=25))

    # 所定のレンダラーで図を表示
    fig.show(renderer=RENDERER)
Hide code cell content
def add_years_to_df(
    df: pd.DataFrame, unit_years: int = UNIT_YEARS, col_date: str = "date"
) -> pd.DataFrame:
    """
    データフレームにunit_years単位で区切った年数を示す新しい列を追加

    Parameters
    ----------
    df : pd.DataFrame
        入力データフレーム
    unit_years : int, optional
        年数を区切る単位、デフォルトはUNIT_YEARS
    col_date : str, optional
        日付を含むカラム名、デフォルトは "date"

    Returns
    -------
    pd.DataFrame
        新しい列が追加されたデータフレーム
    """

    # 入力データフレームをコピー
    df_new = df.copy()

    # unit_years単位で年数を区切り、新しい列として追加
    df_new["years"] = (
        pd.to_datetime(df_new[col_date]).dt.year // unit_years * unit_years
    )

    # 'years'列のデータ型を文字列に変更
    df_new["years"] = df_new["years"].astype(str)

    return df_new
Hide code cell content
def resample_df_by_col_and_years(df: pd.DataFrame, col: str) -> pd.DataFrame:
    """
    指定されたカラムと年数に基づき、データフレームを再サンプル
    colとyearsの全ての組み合わせが存在するように0埋めを行う
    この処理は、作図時にX軸方向の順序が変わることを防ぐために必要

    Parameters
    ----------
    df : pd.DataFrame
        入力データフレーム
    col : str
        サンプリング対象のカラム名

    Returns
    -------
    pd.DataFrame
        再サンプルされたデータフレーム
    """

    # 入力データフレームを新しい変数にコピー
    df_new = df.copy()

    # データフレームからユニークな年数一覧を取得
    unique_years = df["years"].unique()

    # データフレームからユニークなcolの値一覧を取得
    unique_vals = df[col].unique()

    # 一意なカラムの値と年数の全ての組み合わせに対して処理
    for val, years in itertools.product(unique_vals, unique_years):
        # 対象のカラムの値と年数が一致するデータを抽出
        df_tmp = df_new[(df_new[col] == val) & (df_new["years"] == years)]

        # 該当するデータが存在しない場合
        if df_tmp.shape[0] == 0:
            # 0埋めのデータを作成
            default_data = {c: 0 for c in df_tmp.columns}
            # col列についてはvalで埋める
            default_data[col] = val
            # years列についてはyearで埋める
            default_data["years"] = years
            # 新たなレコードとして追加
            df_add = pd.DataFrame(default_data, index=[0])

            # 0埋めのデータをデータフレームに追加
            df_new = pd.concat([df_new, df_add], ignore_index=True)

    return df_new
Hide code cell content
def calculate_tick_values(max_value: float, interval: int) -> tuple:
    """
    与えられた最大値と区切りの間隔に基づいて、tickvalsとticktextを計算する関数

    Parameters
    ----------
    max_value : float
        最大値(声優数など)
    interval : int
        メモリの区切り間隔(例:200)

    Returns
    -------
    tuple
        tickvals(メモリの位置)とticktext(表示するテキスト)のリストを含むタプル
    """
    # 最大値をintervalの倍数に丸める
    rounded_max = round(max_value / interval) * interval

    # 正のメモリ値を生成
    positive_ticks = list(range(0, rounded_max + interval, interval))

    # 負のメモリ値を生成(正の値を反転)
    negative_ticks = [-x for x in positive_ticks][::-1]

    # tickvalsとticktextのリストを結合
    tickvals = negative_ticks + positive_ticks[1:]
    ticktext = [abs(x) for x in tickvals]

    return tickvals, ticktext
Hide code cell content
def save_df_to_csv(df: pd.DataFrame, dir_save: Path, fn_save: str) -> None:
    """
    DataFrameをCSVファイルとして指定されたディレクトリに保存する関数

    Parameters
    ----------
    df : pd.DataFrame
        保存対象となるDataFrame
    dir_save : Path
        出力先ディレクトリのパス
    fn_save : str
        保存するCSVファイルの名前(拡張子は含めない)
    """
    # 出力先ディレクトリが存在しない場合は作成
    dir_save.mkdir(parents=True, exist_ok=True)

    # 出力先のパスを作成
    p_save = dir_save / f"{fn_save}.csv"

    # DataFrameをCSVファイルとして保存する
    df.to_csv(p_save, index=False, encoding="utf-8-sig")

    # 保存完了のメッセージを表示する
    print(f"DataFrame is saved as '{p_save}'.")

可視化例#

マンガデータ#

Hide code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_cc_crt = pd.read_csv(DIR_CM / FN_CC_CRT)
Hide code cell content
# crtidごとのmcid数を集計し、n_mc列として追加した中間DataFrame
df_tmp = df_cc_crt.groupby("crtid")["mcid"].nunique().reset_index(name="n_mc")
# 複数のマンガ雑誌に掲載経験のあるcrtid一覧をリスト化
crtids2drop = df_tmp[df_tmp["n_mc"] > 1]["crtid"].to_list()
# df_cc_crtからcrtid2dropに含まれる行を削除
df_cc_crt = df_cc_crt[~df_cc_crt["crtid"].isin(crtids2drop)].reset_index(drop=True)
Hide code cell content
# 各crtidに対して、関連するmcnameのユニークな数を数える
# groupbyでcrtidごとにグループ化し、nuniqueメソッドで各グループのmcnameのユニークな数を数える
# all関数を使って、nuniqueの結果が全て1であること(各crtidが1つのmcnameにのみ紐づいていること)を確認
assert all(df_cc_crt.groupby("crtid")["mcname"].nunique() == 1)
Hide code cell content
# マンガ雑誌ごとのユニークな原作者数を集計

# マンガ雑誌名(mcname)ごとに、ユニークな原作者名(crtname)の数を集計
# reset_index(name="n_crt")を使用して集計結果を新しいデータフレームに変換し、
# 集計された原作者数を "n_crt" という列名で保存
df_cm = df_cc_crt.groupby("mcname")["crtname"].nunique().reset_index(name="n_crt")

# 全マンガ雑誌の合計作者数に対する比率を計算し、ratio列として追加
# 可視化時に見やすいよう、ratio順にソート
df_cm["ratio"] = df_cm["n_crt"] / df_cm["n_crt"].sum()
df_cm = df_cm.sort_values("ratio", ignore_index=True)

# 棒グラフ中への表示用に、小数点以下2桁で丸めた数値をtext列として追加
df_cm["text"] = df_cm["ratio"].apply(lambda x: f"{x: .2}")

# 列名をわかりやすく変更
df_cm = df_cm.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "n_crt": "作者数",
        "ratio": "マンガ作者数のシェア",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_cm.head()
マンガ雑誌名 作者数 マンガ作者数のシェア text
0 週刊少年サンデー 593 0.206836 0.21
1 週刊少年マガジン 670 0.233694 0.23
2 週刊少年チャンピオン 755 0.263341 0.26
3 週刊少年ジャンプ 849 0.296128 0.3
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_cm, DIR_OUT_CM, "cm")
DataFrame is saved as '../../../data/cm/output/vol2/03/bar/cm.csv'.
Hide code cell source
# 棒グラフを作成し、マンガ雑誌の作者数のシェアを可視化

# x軸にシェア、y軸にマンガ雑誌名を設定
# orientation="h"で棒グラフを水平方向にし、text="text"で小数点以下2桁のシェアを表示
fig = px.bar(
    df_cm,
    x="マンガ作者数のシェア",
    y="マンガ雑誌名",
    orientation="h",
    text="text",
)

# 棒グラフを表示
show_fig(fig)
Hide code cell content
# 追加で必要なデータの読み込み
# pandasのread_csv関数でCSVファイルの読み込み
df_ce = pd.read_csv(DIR_CM / FN_CE)
Hide code cell content
# df_ceに年代情報を追加するための関数add_years_to_dfを適用
# これにより、df_ceに年代情報が含まれるようになる
df_ce = add_years_to_df(df_ce)

# 年代(years)とccidのユニークな組み合わせを持つデータフレームを作成
# 重複するデータを削除し、必要なカラムのみを選択
# ignore_index=Trueで新しいインデックスを割り当てている
df_cc_years = df_ce.drop_duplicates(subset=["years", "ccid"], ignore_index=True)[
    ["ccid", "years"]
]
Hide code cell content
# df_cc_crtとdf_cc_yearsをccidを基準にして右結合(right join)し、df_mergeを作成
# これにより、df_cc_crtのデータにdf_cc_yearsの年代情報が組み合わされる
df_merge = pd.merge(df_cc_crt, df_cc_years, on="ccid", how="right")

# df_mergeをマンガ雑誌名と年代ごとにグループ化し、作者のユニーク数を集計
# nunique()を使用して各グループ内のユニークな作者数をカウント
df_cm2 = (
    df_merge.groupby(["mcname", "years"])["crtid"].nunique().reset_index(name="n_crt")
)

# 年代別の比率を計算するための一時的なDataFrameを作成し、マージ
df_tmp = df_cm2.groupby("years")["n_crt"].sum().reset_index(name="years_total")
df_cm2 = pd.merge(df_cm2, df_tmp, how="left", on="years")

# 年代別のマンガ作者数のシェアを計算
df_cm2["ratio"] = df_cm2["n_crt"] / df_cm2["years_total"]
# 可視化した際の補足情報として、小数点以下2桁で丸めたシェアを計算
df_cm2["text"] = df_cm2["ratio"].apply(lambda x: f"{x: .2}")

# 列名をわかりやすい名前に変更
df_cm2 = df_cm2.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "years": "年代",
        "n_crt": "マンガ作者数",
        "ratio": "マンガ作者数のシェア",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_cm2.head()
マンガ雑誌名 年代 マンガ作者数 years_total マンガ作者数のシェア text
0 週刊少年サンデー 1970 95 493 0.192698 0.19
1 週刊少年サンデー 1980 157 682 0.230205 0.23
2 週刊少年サンデー 1990 147 650 0.226154 0.23
3 週刊少年サンデー 2000 162 787 0.205845 0.21
4 週刊少年サンデー 2010 179 881 0.203178 0.2
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_cm2, DIR_OUT_CM, "cm2")
DataFrame is saved as '../../../data/cm/output/vol2/03/bar/cm2.csv'.
Hide code cell source
# 積上げ棒グラフを作成し、マンガ雑誌ごとの作者数のシェアを可視化

# x軸にマンガ作者数のシェア、y軸に年代を設定し、barmode="stack"で積上げ棒グラフを指定
# colorにマンガ雑誌名を指定して、異なる雑誌を色分け
# orientation="h"で棒グラフを水平方向にし、text="text"でシェアを表示
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_cm2,
    x="マンガ作者数のシェア",
    y="年代",
    barmode="stack",
    color="マンガ雑誌名",
    orientation="h",
    text="text",
    color_discrete_sequence=OKABE_ITO,
)

# 作成した棒グラフを表示
show_fig(fig)
Hide code cell source
# 積上げ棒グラフを作成し、マンガ雑誌ごとの作者数を可視化

# x軸にマンガ作者数、y軸に年代を設定し、barmode="stack"で積み上げ棒グラフを指定
# colorにマンガ雑誌名を指定して、異なる雑誌を色分け
# orientation="h"で棒グラフを水平方向にし、text="マンガ作者数"を指定
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_cm2,
    x="マンガ作者数",
    y="年代",
    barmode="stack",
    color="マンガ雑誌名",
    orientation="h",
    text="マンガ作者数",
    color_discrete_sequence=OKABE_ITO,
)

# 積上げ棒グラフを再表示
show_fig(fig)
Hide code cell source
# 集合棒グラフを作成し、マンガ雑誌ごとの作者数を可視化

# x軸にマンガ作者数、y軸に年代を設定し、barmode="group"で集合棒グラフを指定
# colorにマンガ雑誌名を指定して、異なる雑誌を色分け
# orientation="h"で棒グラフを水平方向にし、text="マンガ作者数"を指定
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_cm2,
    x="マンガ作者数",
    y="年代",
    barmode="group",
    color="マンガ雑誌名",
    orientation="h",
    text="マンガ作者数",
    height=500,
    color_discrete_sequence=OKABE_ITO,
)

# 集合棒グラフを再表示
show_fig(fig)
Hide code cell source
# 集合棒グラフを作成し、マンガ雑誌ごとの作者数を可視化

# x軸にマンガ作者数、y軸にマンガ雑誌名を設定し、barmode="group"で集合棒グラフを指定
# colorに年代を指定して、異なる雑誌を色分け
# orientation="h"で棒グラフを水平方向にし、text="マンガ作者数"を指定
# color_discrete_sequenceでカラースキームを設定
fig = px.bar(
    df_cm2,
    x="マンガ作者数",
    y="マンガ雑誌名",
    color="年代",
    barmode="group",
    orientation="h",
    text="マンガ作者数",
    height=500,
    color_discrete_sequence=px.colors.diverging.Portland,
)

# 集合棒グラフを再表示
show_fig(fig)

アニメデータ#

Hide code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ac_act = pd.read_csv(DIR_AN / FN_AC_ACT)
Hide code cell content
# 各crtidに対して、関連するgenderのユニークな数を数える
# groupbyでcrtidごとにグループ化し、nuniqueメソッドで各グループのgenderのユニークな数を数える
# all関数を使って、nuniqueの結果が全て1であること(各crtidが1つのgenderにのみ紐づいていること)を確認
assert all(df_ac_act.groupby("actid")["gender"].nunique() == 1)
Hide code cell content
# 性別ごとの声優数を集計

# 性別を基準にグループ化して、ユニークな声優IDの数(声優数)をカウント
df_an = df_ac_act.groupby("gender")["actid"].nunique().reset_index(name="n_act")

# 全声優数に対する比率を計算し、ratio列として追加
# 可視化時に見やすいよう、ratio順にソート
df_an["ratio"] = df_an["n_act"] / df_an["n_act"].sum()
df_an = df_an.sort_values("ratio", ignore_index=True)

# 棒グラフへの表示用に、小数点以下2桁で丸めた数値をtext列として追加
df_an["text"] = df_an["ratio"].apply(lambda x: f"{x: .2}")

# カラム名をわかりやすく変更
df_an = df_an.rename(
    columns={"gender": "性別", "n_act": "声優数", "ratio": "声優数のシェア"}
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_an.head()
性別 声優数 声優数のシェア text
0 male 1334 0.444963 0.44
1 female 1664 0.555037 0.56
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_an, DIR_OUT_AN, "an")
DataFrame is saved as '../../../data/an/output/vol2/03/bar/an.csv'.
Hide code cell source
# 棒グラフを作成し、性別のシェアを可視化

# x軸にシェア、y軸に性別を設定
# orientation="h"で棒グラフを水平方向にし、text="text"でシェアを表示
fig = px.bar(
    df_an,
    x="声優数のシェア",
    y="性別",
    orientation="h",
    text="text",
)

# 棒グラフを表示
show_fig(fig)
Hide code cell source
# 棒グラフを作成し、性別ごとの声優数を表示

# x軸に声優数、y軸に性別を設定
# orientation="h"で棒グラフを水平方向にし、text="声優数"を指定
fig = px.bar(
    df_an,
    x="声優数",
    y="性別",
    orientation="h",
    text="声優数",
)

# 棒グラフを表示
show_fig(fig)
Hide code cell content
# 追加で必要なデータの読み込み
# pandasのread_csv関数でCSVファイルの読み込み
df_ae = pd.read_csv(DIR_AN / FN_AE)
Hide code cell content
# df_aeに年代情報を追加するための関数add_years_to_dfを適用
# unit_years=5を指定することで、5年単位の年代情報を追加する
df_ae = add_years_to_df(df_ae, unit_years=5)

# acidとyears(5年単位の年代)のユニークな組み合わせを持つDataFrameを作成
# 重複するデータを削除し、必要なカラムのみを選択
# ignore_index=Trueで新しいインデックスを割り当てる
df_ac_years = df_ae.drop_duplicates(subset=["acid", "years"], ignore_index=True)[
    ["acid", "years"]
]
Hide code cell content
# df_ac_actとdf_ac_yearsをacidを基準にして左結合(left join)し、df_mergeを作成
# これにより、df_ac_actのデータにdf_ac_yearsの年代情報が組み合わされる
df_merge = pd.merge(df_ac_act, df_ac_years, on="acid", how="left")

# df_mergeを性別と年代ごとにグループ化し、声優のユニーク数を集計
# nunique()を使用して各グループ内のユニークな声優数をカウント
df_an2 = (
    df_merge.groupby(["gender", "years"])["acid"].nunique().reset_index(name="n_act")
)

# 可視化用に列名を変更
df_an2 = df_an2.rename(columns={"gender": "性別", "years": "年代", "n_act": "声優数"})
Hide code cell content
# 2000年代以降を可視化対象とする
df_an2 = df_an2[df_an2["年代"].astype(int) >= 2000].reset_index(drop=True)
Hide code cell content
# 可視化対象のDataFrameを確認
df_an2.head()
性別 年代 声優数
0 female 2000 561
1 female 2005 844
2 female 2010 856
3 female 2015 597
4 male 2000 552
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_an2, DIR_OUT_AN, "an2")
DataFrame is saved as '../../../data/an/output/vol2/03/bar/an2.csv'.
Hide code cell source
# 積上げ棒グラフを作成し、年代別の性別の内訳を可視化

# x軸に声優数、y軸に年代を設定し、barmode="stack"で積み上げ棒グラフを指定
# colorに性別を指定
# orientation="h"で棒グラフを水平方向にし、text="声優数"を指定
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_an2,
    x="声優数",
    y="年代",
    barmode="stack",
    color="性別",
    orientation="h",
    text="声優数",
    color_discrete_sequence=OKABE_ITO,
)

# 積上げ棒グラフを再表示
show_fig(fig)
Hide code cell content
# 作図用に声優数を加工するためのDataFrameを作成
# df_an2をコピーして利用
df_an3 = df_an2.copy()

# 表示用の声優数
df_an3["text"] = df_an3["声優数"]

# 作図用の声優数
# もし性別が女性であれば右側、男性であれば左側に表示されるように符号を調整
df_an3["声優数"] = df_an3.apply(
    lambda row: row["声優数"] if row["性別"] == "female" else -row["声優数"], axis=1
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_an3.head()
性別 年代 声優数 text
0 female 2000 561 561
1 female 2005 844 844
2 female 2010 856 856
3 female 2015 597 597
4 male 2000 -552 552
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_an3, DIR_OUT_AN, "an3")
DataFrame is saved as '../../../data/an/output/vol2/03/bar/an3.csv'.
Hide code cell source
# 積上げ棒グラフを作成し、年代別の性別の内訳を可視化

# x軸に声優数、y軸に年代を設定し、barmode="overlay"を指定することで
# x=0を基準に左右に棒が伸びる積上げ棒グラフを作図
# barmode="overlay"の場合はデフォルトで半透明になってしまうので、opacity=1を指定
# colorに性別を指定
# orientation="h"で棒グラフを水平方向にし、text="text"を指定
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_an3,
    x="声優数",
    y="年代",
    barmode="overlay",
    opacity=1,
    color="性別",
    orientation="h",
    text="text",
    color_discrete_sequence=OKABE_ITO,
)

# 男性の声優数が負にならないよう、カスタム目盛りを作成
tickvals, ticktext = calculate_tick_values(abs(df_an3["声優数"]).max(), 200)

# x軸の目盛りとテキストをカスタム設定
fig.update_xaxes(tickvals=tickvals, ticktext=ticktext)

# 積上げ棒グラフを再表示
show_fig(fig)

ゲームデータ#

Hide code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_pkg_pf = pd.read_csv(DIR_GM / FN_PKG_PF)
Hide code cell content
# date列をdatetimeオブジェクトに変換して、曜日情報を新たな列としてdf_pkg_pfに追加
df_pkg_pf["weekday"] = pd.to_datetime(df_pkg_pf["date"]).dt.weekday
Hide code cell content
# 各pkgidに対して、関連するweekdayのユニークな数を数える
# groupbyでpkgidごとにグループ化し、nuniqueメソッドで各グループのweekdayのユニークな数を数える
# all関数を使って、nuniqueの結果が全て1であること(各pkgidが1つのweekdayにのみ紐づいていること)を確認
assert all(df_pkg_pf.groupby("pkgid")["weekday"].nunique() == 1)
Hide code cell content
# 曜日ごとのパッケージ数を集計するためのデータ前処理
# 曜日ごとにユニークなパッケージIDの数を集計
df_gm = df_pkg_pf.groupby("weekday")["pkgid"].nunique().reset_index(name="n_pkg")

# 数値で表されている曜日を文字列にマッピング
df_gm["yobi"] = df_gm["weekday"].apply(lambda x: WEEKDAY2YOBI.get(x, None))

# 可視化用に列名を変更
df_gm = df_gm.rename(columns={"n_pkg": "パッケージ数", "yobi": "発売曜日"})

# パッケージ数順に表示されるように指定する場合に利用する辞書を作成
orders_gm_pkg = {
    "発売曜日": df_gm.sort_values("パッケージ数", ascending=False)["発売曜日"].tolist()
}
Hide code cell content
# 可視化対象のDataFrameを確認
df_gm.head()
weekday パッケージ数 発売曜日
0 0 281
1 1 1692
2 2 4515
3 3 22844
4 4 5331
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gm, DIR_OUT_GM, "gm")
DataFrame is saved as '../../../data/gm/output/vol2/03/bar/gm.csv'.
Hide code cell source
# 棒グラフを作成し、ゲームパッケージの発売曜日の内訳を可視化

# x軸にゲームパッケージ数、y軸に曜日を設定
# orientation="h"で棒グラフを水平方向にし、text="パッケージ数"を指定
fig = px.bar(
    df_gm,
    x="パッケージ数",
    y="発売曜日",
    orientation="h",
    text="パッケージ数",
)

# 棒グラフを表示
show_fig(fig)
Hide code cell content
# df_pkg_pfに年代(5年刻み)を追加
df_pkg_pf = add_years_to_df(df_pkg_pf, unit_years=5)
Hide code cell content
# years、weekdayごとにpkgidのユニーク数を集計し、n_pkg列として追加
df_gm2 = (
    df_pkg_pf.groupby(["years", "weekday"])["pkgid"].nunique().reset_index(name="n_pkg")
)

# 数値で表されている曜日を文字列にマッピング
df_gm2["yobi"] = df_gm2["weekday"].apply(lambda x: WEEKDAY2YOBI.get(x, None))

# 可視化用に列名をリネーム
df_gm2 = df_gm2.rename(
    columns={"years": "発売年代", "yobi": "発売曜日", "n_pkg": "パッケージ数"}
)
Hide code cell content
# 1990年代以降を抽出して、可視化対象とする
df_gm2 = df_gm2[df_gm2["発売年代"].astype(int) >= 1990].reset_index(drop=True)
Hide code cell content
# 可視化対象のDataFrameを確認
df_gm2.head()
発売年代 weekday パッケージ数 発売曜日
0 1990 0 34
1 1990 1 81
2 1990 2 66
3 1990 3 122
4 1990 4 1559
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gm2, DIR_OUT_GM, "gm2")
DataFrame is saved as '../../../data/gm/output/vol2/03/bar/gm2.csv'.
Hide code cell source
# 積上げ棒グラフを作成し、年代別の発売曜日の内訳を可視化

# x軸にゲームパッケージ数、y軸に年代を設定し、barmode="stack"で積上げ棒グラフを指定
# colorに曜日を指定
# orientation="h"で棒グラフを水平方向にし、text="パッケージ数"を指定
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_gm2,
    x="パッケージ数",
    y="発売年代",
    barmode="stack",
    color="発売曜日",
    orientation="h",
    text="パッケージ数",
    color_discrete_sequence=OKABE_ITO,
)

# 積上げ棒グラフを表示
show_fig(fig)
Hide code cell source
# 集合棒グラフを作成し、年代別の発売曜日の内訳を可視化

# x軸にゲームパッケージ数、y軸に年代を設定し、barmode="group"で集合棒グラフを指定
# colorに曜日を指定
# orientation="h"で棒グラフを水平方向に指定
# 棒が多いため、height=800と設定して図の高さに余裕をもたせる
# color_discrete_sequenceでOKABE_ITOカラースキームを設定
fig = px.bar(
    df_gm2,
    x="パッケージ数",
    y="発売年代",
    barmode="group",
    color="発売曜日",
    orientation="h",
    height=800,
    color_discrete_sequence=OKABE_ITO,
)

# 積上げ棒グラフを再表示
show_fig(fig)
Hide code cell source
# 複数の棒グラフを作成し、年代別の発売曜日の内訳を可視化

# x軸にゲームパッケージ数、y軸に年代を設定
# orientation="h"で棒グラフを水平方向に指定
# facet_colとして曜日を指定し、facet_col_wrapで2列を指定
# 棒が多いため、height=800と設定して図の高さに余裕をもたせる
fig = px.bar(
    df_gm2,
    x="パッケージ数",
    y="発売年代",
    facet_col="発売曜日",
    facet_col_wrap=2,
    orientation="h",
    height=800,
)

# ファセット(曜日ごとの棒グラフ)のタイトルを簡潔にする処理
# デフォルトではタイトルは「発売曜日=xxx」という形式になっている
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 棒グラフを再表示
show_fig(fig)