ヒートマップ#

準備#

Import#

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

# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Hide code cell content
# itertoolsモジュールのインポート
# 様々なパターンのループを効率的に実行可能
import itertools

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

# typingモジュールからの型ヒント関連のインポート
# 関数やクラスの引数・返り値の型を注釈するためのツール
from typing import Any, Dict, List, Optional, Union

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

# Comic CollectionとCReaTor関連のファイル名
FN_CC_CRT = "cm_cc_crt.csv"

# Comic Episode関連のファイル名
FN_CE = "cm_ce.csv"

# Anime CollectionとCReaTor関連のファイル名
FN_AC_ACT = "an_ac_act.csv"

# Anime Episode関連のファイル名
FN_AE = "an_ae.csv"

# PacKaGeとPlatForm関連のファイル名
FN_PKG_PF = "gm_pkg_pf.csv"
Hide code cell content
# 可視化に関する設定値の定義

# 可視化対象のマンガ作者数
N_CRT = 20

# 可視化対象のアニメ作品数
N_AC = 20

# 可視化対象のゲームプラットフォーム数
N_PF = 20

# 「年代」の集計単位
UNIT_YEARS = 1
Hide code cell content
# plotlyの描画設定の定義

# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"

関数#

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 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
def format_cols(df: pd.DataFrame, cols_rename: Dict[str, str]) -> pd.DataFrame:
    """
    指定されたカラムのみをデータフレームから抽出し、カラム名をリネームする関数

    Parameters
    ----------
    df : pd.DataFrame
        入力データフレーム
    cols_rename : Dict[str, str]
        リネームしたいカラム名のマッピング(元のカラム名: 新しいカラム名)

    Returns
    -------
    pd.DataFrame
        カラムが抽出・リネームされたデータフレーム
    """

    # 指定されたカラムのみを抽出し、リネーム
    df = df[cols_rename.keys()].rename(columns=cols_rename)

    return df

可視化例#

マンガデータ#

Hide code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ce = pd.read_csv(DIR_CM / FN_CE)
df_cc_crt = pd.read_csv(DIR_CM / FN_CC_CRT)
Hide code cell content
# df_ceに年代情報を追加
df_ce = add_years_to_df(df_ce)

# ccidと年代ごとに話数をカウントし、df_ccを作成
df_cc = df_ce.groupby("ccid")["years"].value_counts().reset_index(name="n_ce")

# df_cc_crtから必要なカラムのみを選択
df_cc_crt = df_cc_crt[["ccid", "crtid", "crtname"]]

# df_ccとdf_cc_crtをマージし、df_cmを作成
# その後、マンガ作者名と年代ごとに話数を集計
df_cm = pd.merge(df_cc, df_cc_crt, on="ccid", how="left")
df_cm = df_cm.groupby(["crtname", "years"])["n_ce"].sum().reset_index()

# ソート用にcrtnameと初出年を対応付けた辞書を作成
crtname2fyears = df_cm.groupby("crtname")["years"].min().to_dict()

# 各マンガ作者ごとの合計話数を計算
# その後、合計話数が上位のマンガ作者名を抽出
df_tmp = df_cm.groupby("crtname")["n_ce"].sum().reset_index()
crtnames = list(df_tmp.sort_values("n_ce", ascending=False)["crtname"].head(N_CRT))
# 上位のマンガ作者のデータのみを選択
df_cm = df_cm[df_cm["crtname"].isin(crtnames)].reset_index(drop=True)
Hide code cell content
# crtnameとyearsでデータをアップサンプリング
df_cm = resample_df_by_col_and_years(df_cm, "crtname")

# crtname列に基づき、crtname2fyearsでfyears列を追加
df_cm["fyears"] = df_cm["crtname"].map(crtname2fyears)
# fyears, years, crtnameでソート
df_cm = df_cm.sort_values(["fyears", "years", "crtname"], ignore_index=True)

# カラム名をわかりやすい名称に変更
cols_rename = {
    "crtname": "マンガ作者名",
    "years": "掲載年",
    "fyears": "初掲載年",
    "n_ce": "合計話数",
}
df_cm = format_cols(df_cm, cols_rename)
Hide code cell content
# 可視化対象のDataFrameを確認
df_cm.head()
マンガ作者名 掲載年 初掲載年 合計話数
0 ちばてつや 1970 1970 13
1 手塚治虫 1970 1970 18
2 梶原一騎 1970 1970 83
3 水島新司 1970 1970 22
4 ちばてつや 1971 1970 44
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_cm, DIR_OUT_CM, "cm")
DataFrame is saved as '../../../data/cm/output/vol2/01/heatmap/cm.csv'.
Hide code cell source
# ヒートマップを用いてデータを可視化

# plotly.expressのdensity_heatmap関数を使用してヒートマップを作成
# x軸には「掲載年」、y軸には「マンガ作者名」、色の濃さ(z値)には「合計話数」を設定、高さを調整
fig = px.density_heatmap(df_cm, x="掲載年", y="マンガ作者名", z="合計話数", height=500)

# 作成したヒートマップを表示
show_fig(fig)
Hide code cell source
# ヒートマップを用いてデータを可視化

# データフレームdf_cmを「マンガ作者名」と「掲載年」でソート
# その後、plotly.expressのdensity_heatmap関数を使用してヒートマップを作成
# x軸には「掲載年」、y軸には「マンガ作者名」、色の濃さ(z値)には「合計話数」を設定、高さを調整
fig = px.density_heatmap(
    df_cm.sort_values(["マンガ作者名", "掲載年"]),
    x="掲載年",
    y="マンガ作者名",
    z="合計話数",
    height=500,
)

# 作成したヒートマップを表示
show_fig(fig)

アニメデータ#

Hide code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ae = pd.read_csv(DIR_AN / FN_AE)
Hide code cell content
# add_years_to_df関数を使用して、df_aeに年代の情報を追加
df_ae = add_years_to_df(df_ae)

# asidごとにaeidのユニーク数を集計しreset_indexでdataframe化
df_an = df_ae.groupby(["asid", "years"])["aeid"].nunique().reset_index()

# 合計話数が多い上位N_ACのasidを抽出
df_tmp = df_an.groupby("asid")["aeid"].sum().reset_index()
asids = list(df_tmp.sort_values("aeid", ascending=False)["asid"].head(N_AC))
# 上位N_ACのasidのみをdf_anから抽出
df_an = df_an[df_an["asid"].isin(asids)].reset_index(drop=True)

# アニメシリーズIDと年代でアップサンプリング
# これにより、すべての作品がすべての年代でデータを持つようになる
df_an = resample_df_by_col_and_years(df_an, "asid")
Hide code cell content
# asidごとに初放送日を対応付けた辞書を作成し
asid2fdate = df_ae.groupby("asid")["date"].min().to_dict()
# asid列に基づきasid2fdateでマッピングした結果をfdate列に格納
df_an["fdate"] = df_an["asid"].map(asid2fdate)

# dateが最も若いacnameを便宜上asnameとし、asidとasnameを紐づける辞書を作成
asid2asname = df_ae.sort_values("date").groupby("asid")["acname"].first().to_dict()
# asid列に基づきasid2asnameでマッピングした結果をasname列に格納
df_an["asname"] = df_an["asid"].map(asid2asname)

# 可視化のためにfdateとyearsでソートし
df_an = df_an.sort_values(["fdate", "years"], ignore_index=True)

# 可視化用に列名を変更
cols_rename = {
    "asname": "代表的なアニメ作品名",
    "asid": "アニメシリーズID",
    "years": "放送年",
    "fdate": "初放送日",
    "aeid": "合計話数",
}
df_an = format_cols(df_an, cols_rename)
Hide code cell content
# 可視化対象のDataFrameを確認
df_an.head()
代表的なアニメ作品名 アニメシリーズID 放送年 初放送日 合計話数
0 クレヨンしんちゃん C1327 1992 1992-04-13 106
1 クレヨンしんちゃん C1327 1993 1992-04-13 159
2 クレヨンしんちゃん C1327 1994 1992-04-13 158
3 クレヨンしんちゃん C1327 1995 1992-04-13 156
4 クレヨンしんちゃん C1327 1996 1992-04-13 142
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_an, DIR_OUT_AN, "an")
DataFrame is saved as '../../../data/an/output/vol2/01/heatmap/an.csv'.
Hide code cell source
# ヒートマップを作成するためにplotlyのdensity_heatmap関数を使用
# x軸には放送年、y軸にはアニメ作品名を配置し、色の濃さで合計話数を表現、高さを調整
fig = px.density_heatmap(
    df_an, x="放送年", y="代表的なアニメ作品名", z="合計話数", height=500
)

# 作成したヒートマップを表示
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
# df_pkg_pfに年代情報を追加
df_pkg_pf = add_years_to_df(df_pkg_pf)

# プラットフォーム名と発売年ごとにパッケージ数をカウントし、df_gmを作成
df_gm = (
    df_pkg_pf.groupby(["pfname", "years"])["pkgid"].nunique().reset_index(name="n_pkg")
)

# 各プラットフォームごとの合計パッケージ数を計算
# その後、合計パッケージ数が上位のプラットフォーム名を抽出
df_tmp = df_gm.groupby("pfname")["n_pkg"].sum().reset_index()
pfnames = list(df_tmp.sort_values("n_pkg", ascending=False)["pfname"].head(N_PF))
# 上位のプラットフォームのデータのみを抽出
df_gm = df_gm[df_gm["pfname"].isin(pfnames)].reset_index(drop=True)
Hide code cell content
# 可視化用にpfnameとyearsでデータをアップサンプリング
df_gm = resample_df_by_col_and_years(df_gm, "pfname")

# プラットフォーム名と初出の年代を紐づけた辞書を作成し、fdate列として追加
pfname2fdate = df_pkg_pf.groupby("pfname")["date"].min().to_dict()
df_gm["fdate"] = df_gm["pfname"].map(pfname2fdate)
# fdate, yearsでソート
df_gm = df_gm.sort_values(["fdate", "years"], ignore_index=True)

# 可視化用にカラム名を変更
cols_rename = {
    "pfname": "プラットフォーム名",
    "years": "発売年",
    "fdate": "初発売日",
    "n_pkg": "合計パッケージ数",
}
df_gm = format_cols(df_gm, cols_rename)
Hide code cell content
# 可視化対象のDataFrameを確認
df_gm.head()
プラットフォーム名 発売年 初発売日 合計パッケージ数
0 メガドライブ 1988 1988-10-29 4
1 メガドライブ 1989 1988-10-29 21
2 メガドライブ 1990 1988-10-29 52
3 メガドライブ 1991 1988-10-29 67
4 メガドライブ 1992 1988-10-29 76
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gm, DIR_OUT_GM, "gm")
DataFrame is saved as '../../../data/gm/output/vol2/01/heatmap/gm.csv'.
Hide code cell source
# データをヒートマップとして可視化

# density_heatmapを使用してヒートマップを作成
# x軸には"発売年"、y軸には"プラットフォーム名"、色の濃淡で"合計パッケージ数"を表示、高さを調整
fig = px.density_heatmap(
    df_gm,
    x="発売年",
    y="プラットフォーム名",
    z="合計パッケージ数",
    height=500,
)

# ヒートマップを表示
show_fig(fig)
Hide code cell content
# プラットフォーム名にプレイステーションを含むもののみ抽出
df_gm2 = df_gm[
    df_gm["プラットフォーム名"].str.contains("プレイステーション")
].reset_index(drop=True)
Hide code cell content
# 可視化対象のDataFrameを確認
df_gm2.head()
プラットフォーム名 発売年 初発売日 合計パッケージ数
0 プレイステーション 1988 1994-12-03 0
1 プレイステーション 1989 1994-12-03 0
2 プレイステーション 1990 1994-12-03 0
3 プレイステーション 1991 1994-12-03 0
4 プレイステーション 1992 1994-12-03 0
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gm2, DIR_OUT_GM, "gm2")
DataFrame is saved as '../../../data/gm/output/vol2/01/heatmap/gm2.csv'.
Hide code cell source
# プレイステーションに関するデータをヒートマップとして可視化

# density_heatmapを使用してヒートマップを作成
# x軸には"発売年"、y軸には"プラットフォーム名"、色の濃淡で"合計パッケージ数"を表示
fig = px.density_heatmap(
    df_gm2, x="発売年", y="プラットフォーム名", z="合計パッケージ数"
)

# ヒートマップを表示
show_fig(fig)