メディア展開データの関係を見る#

準備#

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のインポート
# より詳細なグラフ作成機能を利用可能
# goという名前で参照可能
import plotly.graph_objects as go

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

# plotly.subplotsからmake_subplotsのインポート
# 複数のサブプロットを含む複合的な図を作成する際に使用
from plotly.subplots import make_subplots

変数#

Hide code cell content
# メディア展開データが保存されているディレクトリのパス
DIR_IN = Path("../../../data/mix/input/")

# 分析結果の出力先ディレクトリのパス
DIR_OUT = (
    DIR_IN.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "assocs"
)
Hide code cell content
# 読み込み対象ファイル名の定義

# アニメ各話と原作マンガの作者の対応関係に関するファイル
FN_AE_CRT = "mix_ae_crt.csv"

# マンガ各話とアニメ作品の対応関係に関するファイル
FN_CE_AC = "mix_ce_ac.csv"
Hide code cell content
# plotlyの描画設定の定義

# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"
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 = 10, col_date: str = "date"
) -> pd.DataFrame:
    """
    データフレームにunit_years単位で区切った年数を示す新しい列を追加

    Parameters
    ----------
    df : pd.DataFrame
        入力データフレーム
    unit_years : int, optional
        年数を区切る単位、デフォルトは10
    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 create_connectedplot(
    df: pd.DataFrame, x: str, y: str, text: str, color: str, **args
) -> Figure:
    """
    データフレームから連結散布図を作成する

    Parameters
    ----------
    df : pd.DataFrame
        グラフに使用するデータを含むpandasデータフレーム
    x : str
        x軸に使用するデータフレームの列名
    y : str
        y軸に使用するデータフレームの列名
    text : str
        プロット上で表示するテキストを含むデータフレームの列名
    color : str
        プロットのカラーマッピングに使用されるデータフレームの列名
        カラーバーのタイトルとしても使用される
    **args
        追加のキーワード引数。これらはPlotly Express関数に渡される

    Returns
    -------
    fig : plotly.graph_objs._figure.Figure
        生成されたPlotlyのFigureオブジェクト
    """

    # 折れ線グラフを使って連結散布図の基礎を作成する
    fig_line = px.line(df, x=x, y=y, text=text, **args)

    # 散布図を作成し、連結散布図のポイントとして折れ線グラフに追加する
    fig_scatter = px.scatter(df, x=x, y=y, color=color, **args)

    # 散布図のトレースを折れ線グラフに追加し、接続点を表現する
    for trace in fig_scatter.data:
        fig_line.add_trace(trace)

    # 折れ線グラフのスタイルを更新し、視覚的特徴を強化する
    fig_line.update_traces(
        line={"color": "grey"},
        marker={"size": 15, "line_width": 1, "opacity": 0.7},
        textposition="bottom right",
    )

    # グラフのレイアウトを更新し、カラーバーのタイトルを`color`引数に基づいて設定する
    fig_line.update_layout(coloraxis_colorbar=dict(title=color))

    return fig_line
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_ce_ac = pd.read_csv(DIR_IN / FN_CE_AC)

散布図#

Hide code cell content
# 最小のマンガ各話数を設定
min_nce = 8

# df_ce_acデータフレームを日付で昇順に並び替え、新しいインデックスを割り当て
df_ce_ac = df_ce_ac.sort_values("date", ignore_index=True)

# 'first_date_cc'列のデータを日付型に変換
df_ce_ac["first_date_cc"] = pd.to_datetime(df_ce_ac["first_date_cc"])

# n_ceがmin_nce以上かつfirst_date_ccの年が1990年以降のレコードをフィルタリング
df_tmp = df_ce_ac[
    (df_ce_ac["n_ce"] >= min_nce) & (df_ce_ac["first_date_cc"].dt.year >= 1990)
].reset_index(drop=True)

# 各ccidについて、最初のmin_nce個のレコードのみを保持
df_tmp = df_tmp.groupby("ccid").head(min_nce).reset_index(drop=True)

# 'acid'列に欠損値がない場合(アニメーションが存在する場合)にTrueを設定し、
# 新しい列'is_animated'をデータフレームに追加
df_tmp["is_animated"] = ~df_tmp["acid"].isna()
Hide code cell content
# df_tmpデータフレームを'mcname'、'ccid'、'ccname'でグループ化し、
# それぞれのグループにおける以下の列の平均値を計算:
# 'page_start_position'、'pages'、'four_colored'、'n_ce'、
# 'is_animated'、'first_date_cc'
df_scat = (
    df_tmp.groupby(["mcname", "ccid", "ccname"])[
        [
            "page_start_position",
            "pages",
            "four_colored",
            "n_ce",
            "is_animated",
            "first_date_cc",
        ]
    ]
    .mean()
    .reset_index()
)

# 'is_animated'列が1の場合はTrueに、それ以外の場合はFalseに変換
# これにより、'is_animated'列を論理値(TrueまたはFalse)で表現
df_scat["is_animated"] = df_scat["is_animated"] == 1

# データフレームの列名をより分かりやすい名称に変更
df_scat = df_scat.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "ccname": "マンガ作品名",
        "page_start_position": f"{min_nce}話目までの平均掲載位置",
        "pages": f"{min_nce}話目までの平均ページ数",
        "four_colored": f"{min_nce}話目までのカラー獲得率",
        "n_ce": "合計各話数",
        "is_animated": "アニメ化",
        "first_date_cc": "連載開始日",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_scat.head()
マンガ雑誌名 ccid マンガ作品名 8話目までの平均掲載位置 8話目までの平均ページ数 8話目までのカラー獲得率 合計各話数 アニメ化 連載開始日
0 週刊少年サンデー C110901 戦争劇場 0.397316 9.50 0.125 55.0 False 2014-08-06
1 週刊少年サンデー C110913 デジコン 0.237189 30.25 0.375 26.0 False 2014-09-03
2 週刊少年サンデー C110925 おいしい神しゃま 0.654687 4.00 1.000 39.0 False 2014-09-24
3 週刊少年サンデー C110985 トキワ来たれり!! 0.172369 23.25 0.125 120.0 False 2014-12-03
4 週刊少年サンデー C110987 ドリー・マー 0.319380 27.25 0.250 32.0 False 2014-12-10
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_scat, DIR_OUT, "scat")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/scat.csv'.
Hide code cell source
# Plotly Expressを使用して、df_scatデータフレームから散布図を作成
# x軸にはmin_nce話までの平均掲載位置を、y軸にはmin_nce話までの平均ページ数を設定
# 'アニメ化'の値によってOKABE_ITOスケールで色分けし、マーカーの透明度を0.7に設定
# 各マーカーにマウスオーバーした際に表示されるテキストとして、マンガ作品名を指定
fig = px.scatter(
    df_scat,
    x=f"{min_nce}話目までの平均掲載位置",
    y=f"{min_nce}話目までの平均ページ数",
    color="アニメ化",
    opacity=0.7,
    hover_name="マンガ作品名",
    color_discrete_sequence=OKABE_ITO,
)

# 散布図のマーカーをカスタマイズ
# マーカーのサイズを15に設定し、枠線の幅を1、枠線の色を白に設定
fig.update_traces(
    marker={
        "size": 15,
        "line_width": 1,
        "line_color": "white",
    }
)

# 作成した散布図を表示する関数を呼び出し
show_fig(fig)
Hide code cell source
# Plotly Expressを使用して、df_scatデータフレームから散布図を作成
# x軸にはmin_nce話までの平均掲載位置を、y軸にはmin_nce話までの平均ページ数を設定
# 'アニメ化'の値によってOKABE_ITOスケールで色分けし、マーカーの透明度を0.7に設定
# 各マーカーにマウスオーバーした際に表示されるテキストとして、マンガ作品名を指定
# マンガ雑誌名を基準にファセットを分け、facet_col_wrapで列数を指定
fig = px.scatter(
    df_scat,
    x=f"{min_nce}話目までの平均掲載位置",
    y=f"{min_nce}話目までの平均ページ数",
    color="アニメ化",
    opacity=0.7,
    hover_name="マンガ作品名",
    color_discrete_sequence=OKABE_ITO,
    facet_col="マンガ雑誌名",
    facet_col_wrap=2,
)

# 散布図のマーカーをカスタマイズ
# マーカーのサイズを15に設定し、枠線の幅を1、枠線の色を白に設定
fig.update_traces(
    marker={
        "size": 15,
        "line_width": 1,
        "line_color": "white",
    }
)

# ファセットごとのタイトルを簡略化するため、=以降のみを抜き出して表示
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 可視化結果を再表示
show_fig(fig)

バブルチャート#

Hide code cell content
# 散布図と同じデータを利用
df_bub = df_scat.copy()
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bub, DIR_OUT, "bub")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/bub.csv'.
Hide code cell source
# Plotly Expressを使用して、df_bubデータフレームからバブルチャートを作成
# x軸には最初のmin_nce話までの平均掲載位置を、y軸には最初のmin_nce話までの平均ページ数を設定
# マーカーのサイズを最初のmin_nce話までのカラー獲得率に基づいて設定し、
# 'アニメ化'の値によってOKABE_ITOに基づき色分けを行い、透明度を0.7に設定
# 各マーカーにマウスオーバーした際に表示されるテキストとして、マンガ作品名を指定
fig = px.scatter(
    df_bub,
    x=f"{min_nce}話目までの平均掲載位置",
    y=f"{min_nce}話目までの平均ページ数",
    size=f"{min_nce}話目までのカラー獲得率",
    color="アニメ化",
    opacity=0.7,
    hover_name="マンガ作品名",
    color_discrete_sequence=OKABE_ITO,
)

# バブルチャートのマーカーをカスタマイズ
# マーカーの枠線の幅を1、枠線の色を白に設定
fig.update_traces(
    marker={
        "line_width": 1,
        "line_color": "white",
    }
)

# 作成したバブルチャートを表示する関数を呼び出し
show_fig(fig)
Hide code cell source
# Plotly Expressを使用して、df_bubデータフレームからバブルチャートを作成
# x軸には最初のmin_nce話までの平均掲載位置を、y軸には最初のmin_nce話までの平均ページ数を設定
# マーカーのサイズを最初のmin_nce話までのカラー獲得率に基づいて設定し、
# 'アニメ化'の値によってOKABE_ITOに基づき色分けを行い、透明度を0.7に設定
# 各マーカーにマウスオーバーした際に表示されるテキストとして、マンガ作品名を指定
# マンガ雑誌名に基づきファセットを分け、2列で表示
fig = px.scatter(
    df_bub,
    x=f"{min_nce}話目までの平均掲載位置",
    y=f"{min_nce}話目までの平均ページ数",
    size=f"{min_nce}話目までのカラー獲得率",
    color="アニメ化",
    opacity=0.7,
    hover_name="マンガ作品名",
    color_discrete_sequence=OKABE_ITO,
    facet_col="マンガ雑誌名",
    facet_col_wrap=2,
)

# バブルチャートのマーカーをカスタマイズ
# マーカーの枠線の幅を1、枠線の色を白に設定
fig.update_traces(
    marker={
        "line_width": 1,
        "line_color": "white",
    }
)

# ファセットのタイトルを「=」以降(つまり具体的なマンガ雑誌名)のみに簡略化
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成したバブルチャートを表示する関数を呼び出し
show_fig(fig)
Hide code cell content
# df_ce_acデータフレームから、n_ceがmin_nce以上かつfirst_date_ccの年が1990年以降のレコードを選択
df_tmp = df_ce_ac[
    (df_ce_ac["n_ce"] >= min_nce) & (df_ce_ac["first_date_cc"].dt.year >= 1990)
].reset_index(drop=True)

# 各マンガ作品ID(ccid)ごとに、最初のmin_nce個のレコードのみを保持
df_tmp = df_tmp.groupby("ccid").head(min_nce).reset_index(drop=True)

# 'acid'列に欠損値がない場合にTrueを設定し、新しい列'is_animated'をデータフレームに追加
df_tmp["is_animated"] = ~df_tmp["acid"].isna()
Hide code cell content
# df_tmpデータフレームを'mccid'と'date'で昇順に並び替え、新しいインデックスを割り当て
df_motion = df_tmp.sort_values(["ccid", "date"], ignore_index=True)

# 各マンガ作品ID('ccid')ごとに連続した各話番号('ceno')を割り当て
df_motion["ceno"] = df_motion.groupby("ccid").cumcount() + 1

# 各マンガ作品から最初のmin_nce話までのデータのみを保持
df_motion = df_motion[df_motion["ceno"] <= min_nce].reset_index(drop=True)

# 'acid'列に欠損値がない場合にTrueを設定し、新しい列'is_animated'をデータフレームに追加
df_motion["is_animated"] = ~df_motion["acid"].isna()

# 'mcname'(マンガ雑誌名)、'is_animated'(アニメ化されているかどうか)、
# 'ceno'(各話番号)でグループ化し、平均掲載位置、平均ページ数、カラー獲得率、作品数を集計
df_motion = (
    df_motion.groupby(["mcname", "is_animated", "ceno"])
    .agg(
        平均掲載位置=("page_start_position", "mean"),
        平均ページ数=("pages", "mean"),
        カラー獲得率=("four_colored", "mean"),
        作品数=("ceno", "size"),
    )
    .reset_index()
)

# データフレームの列名をより分かりやすい名称に変更
df_motion = df_motion.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "is_animated": "アニメ化",
        "ceno": "話数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_motion.head()
マンガ雑誌名 アニメ化 話数 平均掲載位置 平均ページ数 カラー獲得率 作品数
0 週刊少年サンデー False 1 0.145375 42.392157 0.725490 255
1 週刊少年サンデー False 2 0.276159 26.811765 0.168627 255
2 週刊少年サンデー False 3 0.323798 20.584314 0.125490 255
3 週刊少年サンデー False 4 0.355677 18.094118 0.129412 255
4 週刊少年サンデー False 5 0.390275 17.494118 0.098039 255
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_motion, DIR_OUT, "motion")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/motion.csv'.
Hide code cell source
# Plotly Expressを使用して、df_motionデータフレームからモーションチャートを作成
# x軸に平均掲載位置、y軸にカラー獲得率を設定し、色分けはアニメ化されているかどうかで行う
# マンガ雑誌名ごとに異なるプロットに分け、各プロットのサイズは作品数に基づく
# 各マンガ雑誌名のプロットは2列でラップされ、話数ごとにアニメーションする
# x軸、y軸の表示範囲を固定し、カラースケールとしてOKABE_ITOを選択
fig = px.scatter(
    df_motion,
    x="平均掲載位置",
    y="カラー獲得率",
    color="アニメ化",
    facet_col="マンガ雑誌名",
    size="作品数",
    facet_col_wrap=2,
    animation_frame="話数",
    range_x=[0, 1],
    range_y=[0, 1],
    animation_group="アニメ化",
    color_discrete_sequence=OKABE_ITO,
    height=600,
)

# 各ファセットのタイトルをマンガ雑誌名のみに変更("マンガ雑誌名="を取り除く)
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成したモーションチャートを表示する関数を呼び出し
show_fig(fig)

散布図行列#

Hide code cell content
# df_scatデータフレームのコピーを作成してdf_matに格納
df_mat = df_scat.copy()

# df_matデータフレームの列名を変更
# 散布図行列上に表示するため、各列名から「min_nce話目までの」を削除して簡略化
# 例えば、「8話目までの平均掲載位置」が「平均掲載位置」になる
df_mat.columns = [c.replace(f"{min_nce}話目までの", "") for c in df_mat.columns]
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_mat, DIR_OUT, "mat")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/mat.csv'.
Hide code cell source
# Plotly Expressを使用して、df_matデータフレームから散布図行列を作成
# '平均掲載位置'、'平均ページ数'、'カラー獲得率'、'連載開始日'を次元として使用し、
# 'アニメ化'の値に応じてOKABE_ITOに基づき色分けを行い、透明度を0.6に設定
# 各マーカーにマウスオーバーした際に表示されるテキストとして、マンガ作品名を指定
fig = px.scatter_matrix(
    df_mat,
    dimensions=["平均掲載位置", "平均ページ数", "カラー獲得率", "連載開始日"],
    color="アニメ化",
    opacity=0.6,
    height=600,
    hover_name="マンガ作品名",
    color_discrete_sequence=OKABE_ITO,
)

# 散布図行列のマーカーをカスタマイズ
# マーカーの枠線の幅を1、枠線の色を白に設定
fig.update_traces(
    marker={
        "line_width": 1,
        "line_color": "white",
    }
)

# 作成した散布図行列を表示する関数を呼び出し
show_fig(fig)

二次元ヒストグラム#

Hide code cell content
# 散布図と同じデータを利用
df_2d = df_scat.copy()
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_2d, DIR_OUT, "2d")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/2d.csv'.
Hide code cell source
# 最初のmin_nce話までの平均掲載位置と平均ページ数を表す列名を変数に格納
col_x = f"{min_nce}話目までの平均掲載位置"
col_y = f"{min_nce}話目までの平均ページ数"

# Plotly Expressを使用して、df_2dデータフレームから2次元ヒストグラムを作成
# x軸には平均掲載位置、y軸には平均ページ数を設定
# 'アニメ化'ごとに異なるヒートマップに分け、2列でラップして表示
fig = px.density_heatmap(
    df_2d, x=col_x, y=col_y, facet_col="アニメ化", facet_col_wrap=2
)

# 作成した2次元ヒストグラムを表示する関数を呼び出し
show_fig(fig)
Hide code cell source
# Plotlyのmake_subplots関数を使って、1行2列のサブプロットを持つ図を作成
# 各サブプロットのタイトルを「アニメ化実績なし」と「アニメ化実績あり」に設定
fig = make_subplots(
    rows=1, cols=2, subplot_titles=["アニメ化実績なし", "アニメ化実績あり"]
)

# x軸とy軸のビンの設定を定義
# x軸は0から1まで、y軸は0からdf_2d[col_y]の最大値の1.1倍までを範囲とする
xbin = dict(start=0, end=1, size=0.1)
ybin = dict(start=0, end=df_2d[col_y].max() * 1.1, size=df_2d[col_y].max() / 10)

# df_2dを'アニメ化'の値でグループ化し、各グループに対して2次元ヒストグラムをプロット
# カラースケールの表示を省略し、ビンをそれぞれxbinsとybinsで指定
# 行は必ず1行目、列はアニメ化実績なし=1列目、アニメ化実績あり=2列目
for i, (_, df_tmp) in enumerate(df_2d.groupby("アニメ化")):
    fig.add_trace(
        go.Histogram2d(
            x=df_tmp[col_x],
            y=df_tmp[col_y],
            showscale=False,
            xbins=xbin,
            ybins=ybin,
        ),
        row=1,
        col=i + 1,
    )

# すべてのx軸に対してタイトルをcol_xと設定
fig.update_xaxes(title_text=col_x)
# すべてのy軸に対してタイトルをcol_y設定
fig.update_yaxes(title_text=col_y)

# 作成した図を表示する関数を呼び出し
show_fig(fig)

等値線図#

Hide code cell content
# df_2dデータフレームのコピーを作成してdf_contに格納
df_cont = df_2d.copy()

# df_contデータフレームを'マンガ雑誌名'と'アニメ化'で昇順に並び替え、新しいインデックスを割り当て
df_cont = df_cont.sort_values(["マンガ雑誌名", "アニメ化"], ignore_index=True)
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_cont, DIR_OUT, "cont")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/cont.csv'.
Hide code cell source
# Plotly Expressを使用して、df_contデータフレームから等値線図を作成
# x軸には変数col_x(最初のmin_nce話までの平均掲載位置)、
# y軸には変数col_y(最初のmin_nce話までの平均ページ数)を設定
# 'アニメ化'ごとに異なる等値線図に分け、2列で表示
fig = px.density_contour(
    df_cont, x=col_x, y=col_y, facet_col="アニメ化", facet_col_wrap=2
)

# 等値線図のトレース設定を更新
# 等値線の色を塗りつぶしにし、等値線のラベルを表示
fig.update_traces(contours_coloring="fill", contours_showlabels=True)

# 各トレースのカラースケールを非表示に設定
# これはカラーバーの表示が意図しない形になる可能性があるため
fig.update_traces(showscale=False)

# 作成した等値線図を表示する関数を呼び出し
show_fig(fig)

折れ線グラフ#

Hide code cell content
# df_ce_acデータフレームのコピーを作成してdf_tmpに格納
df_tmp = df_ce_ac.copy()

# 'date'列のデータを日付型に変換
df_tmp["date"] = pd.to_datetime(df_tmp["date"])

# n_ceがmin_nce以上かつdateの年が1990年以降のレコードをフィルタリング
df_tmp = df_tmp[
    (df_tmp["n_ce"] >= min_nce) & (df_tmp["date"].dt.year >= 1990)
].reset_index(drop=True)

# 'acid'列に欠損値がない場合にTrueを設定し、新しい列'is_animated'をデータフレームに追加
df_tmp["is_animated"] = ~df_tmp["acid"].isna()
Hide code cell content
# df_tmpデータフレームを'mcname'(マンガ雑誌名)と'date'(発売日)でグループ化し、
# 'is_animated'(アニメ化されているかどうか)に関する統計情報(合計、平均、カウント)を計算
df_line = (
    df_tmp.groupby(["mcname", "date"])["is_animated"]
    .agg(["sum", "mean", "count"])
    .reset_index()
)

# 合計作品数が5を超える行のみを保持
df_line = df_line[df_line["count"] > 5].reset_index(drop=True)

# データフレームの列名をより分かりやすい名称に変更
df_line = df_line.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "date": "発売日",
        "sum": "アニメ化作品数",
        "mean": "アニメ化作品率",
        "count": "合計作品数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_line.head()
マンガ雑誌名 発売日 アニメ化作品数 アニメ化作品率 合計作品数
0 週刊少年サンデー 1990-01-01 1 0.052632 19
1 週刊少年サンデー 1990-01-02 1 0.050000 20
2 週刊少年サンデー 1990-01-11 1 0.045455 22
3 週刊少年サンデー 1990-01-24 2 0.090909 22
4 週刊少年サンデー 1990-01-31 2 0.100000 20
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_line, DIR_OUT, "line")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/line.csv'.
Hide code cell source
# Plotly Expressを使用して、df_lineデータフレームから折れ線グラフを作成
# x軸には'発売日'、y軸には'アニメ化作品率'を設定し、
# 'マンガ雑誌名'ごとに異なる折れ線グラフに分けて表示
# facet_col_wrap=1により、各マンガ雑誌名ごとに縦に並べて表示
fig = px.line(
    df_line,
    x="発売日",
    y="アニメ化作品率",
    facet_col="マンガ雑誌名",
    facet_col_wrap=1,
    height=500,
)

# ホバーモードを'x'に設定し、同じx位置のデータポイントにマウスを置くと情報が表示されるようにする
fig.update_layout(hovermode="x")

# y軸の範囲を0から1に設定し、アニメ化作品率が0-100%の範囲で表示されるようにする
fig.update_yaxes(range=[0, 1])

# 各ファセットのアノテーション(マンガ雑誌名のラベル)を更新し、
# 不要なプレフィックス("マンガ雑誌名=")を削除して見やすいラベルにする
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# グラフを表示
show_fig(fig)
Hide code cell source
# Plotly Expressを使用して、df_lineデータフレームから積上げ密度プロットを作成
# x軸には'発売日'、y軸には'アニメ化作品率'を設定し、
# 'マンガ雑誌名'ごとに異なる積上げ密度プロットに分けて表示
# facet_col_wrap=1により、各マンガ雑誌名ごとに縦に並べて表示
fig = px.area(
    df_line,
    x="発売日",
    y="アニメ化作品率",
    facet_col="マンガ雑誌名",
    facet_col_wrap=1,
    height=500,
)

# ホバーモードを'x'に設定し、同じx位置のデータポイントにマウスを置くと情報が表示されるようにする
fig.update_layout(hovermode="x")

# y軸の範囲を0から1に設定し、アニメ化作品率が0-100%の範囲で表示されるようにする
fig.update_yaxes(range=[0, 1])

# 各ファセットのアノテーション(マンガ雑誌名のラベル)を更新し、
# 不要なプレフィックス("マンガ雑誌名=")を削除して見やすいラベルにする
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# グラフを表示
show_fig(fig)

連結散布図#

Hide code cell content
# df_ce_acデータフレームから、n_ceがmin_nce以上かつfirst_date_ccの年が1990年以降のレコードを選択
df_tmp = df_ce_ac[
    (df_ce_ac["n_ce"] >= min_nce) & (df_ce_ac["first_date_cc"].dt.year >= 1990)
].reset_index(drop=True)

# 各マンガ作品ID(ccid)ごとに、最初のmin_nce話のレコードのみを保持
df_tmp = df_tmp.groupby("ccid").head(min_nce).reset_index(drop=True)

# 'acid'列に欠損値がない場合にTrueを設定し、新しい列'is_animated'をデータフレームに追加
df_tmp["is_animated"] = ~df_tmp["acid"].isna()

# データフレームを'mccid'(マンガ雑誌ID)と'date'(日付)で昇順に並び替え
df_tmp = df_tmp.sort_values(["ccid", "date"], ignore_index=True)

# 各マンガ作品ID(ccid)ごとに連続した各話番号(ceno)を割り当て
df_tmp["ceno"] = df_tmp.groupby("ccid").cumcount() + 1

# 各マンガ作品から最初のmin_nce話までのデータのみを保持
df_tmp = df_tmp[df_tmp["ceno"] <= min_nce].reset_index(drop=True)

# 再度、'acid'列に欠損値がない場合にTrueを設定し、'is_animated'列を更新
df_tmp["is_animated"] = ~df_tmp["acid"].isna()
Hide code cell content
# df_tmpデータフレームを'is_animated'と'ceno'(各話番号)でグループ化し、
# 平均掲載位置、平均ページ数、カラー獲得率、作品数を集計
df_conn = (
    df_tmp.groupby(["is_animated", "ceno"])
    .agg(
        平均掲載位置=("page_start_position", "mean"),
        平均ページ数=("pages", "mean"),
        カラー獲得率=("four_colored", "mean"),
        作品数=("ceno", "size"),
    )
    .reset_index()
)

# 'ceno'列の値を使用して、各話番号に対応するラベル(例:"1話目")を割り当て
df_conn["text"] = df_conn["ceno"].map(lambda x: f"{x}話目")

# データフレームの列名をより分かりやすい名称に変更
# 'is_animated'を'アニメ化'に、'ceno'を'話数'にリネーム
df_conn = df_conn.rename(columns={"is_animated": "アニメ化", "ceno": "話数"})
Hide code cell content
# 可視化対象のDataFrameを確認
df_conn.head()
アニメ化 話数 平均掲載位置 平均ページ数 カラー獲得率 作品数 text
0 False 1 0.155210 46.352649 0.769868 1208 1話目
1 False 2 0.294062 29.057119 0.230960 1208 2話目
2 False 3 0.379118 21.525662 0.100993 1208 3話目
3 False 4 0.418088 18.772351 0.083609 1208 4話目
4 False 5 0.468384 17.968543 0.067881 1208 5話目
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_conn, DIR_OUT, "conn")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/conn.csv'.
Hide code cell source
# create_connectedplotを使って連結散布図を作成
# x軸には'平均掲載位置'、y軸には'カラー獲得率'を設定し、マーカーの色には'話数'を設定
# 'アニメ化'の値によって異なるグラフに分けて表示
fig = create_connectedplot(
    df_conn,
    x="平均掲載位置",
    y="カラー獲得率",
    color="話数",
    text="text",
    facet_col="アニメ化",
)

# x軸とy軸の範囲を設定
fig.update_xaxes(range=[-0.05, 1.05])
fig.update_yaxes(range=[-0.05, 1.05])

# 作成した図を表示する関数を呼び出し
show_fig(fig)
Hide code cell content
# df_tmpデータフレームを'mcname'(マンガ雑誌名)、'is_animated'(アニメ化されているかどうか)、
# 'ceno'(各話番号)でグループ化し、平均掲載位置、平均ページ数、カラー獲得率、作品数を集計
df_conn2 = (
    df_tmp.groupby(["mcname", "is_animated", "ceno"])
    .agg(
        平均掲載位置=("page_start_position", "mean"),
        平均ページ数=("pages", "mean"),
        カラー獲得率=("four_colored", "mean"),
        作品数=("ceno", "size"),
    )
    .reset_index()
)

# 'ceno'の値を使用して各話番号に対応するラベル(例:"1話目")を割り当て
df_conn2["text"] = df_conn2["ceno"].map(lambda x: f"{x}話目")

# データフレームの列名をより分かりやすい名称に変更
# 'mcname'を'マンガ雑誌名'に、'is_animated'を'アニメ化'に、'ceno'を'話数'にリネーム
df_conn2 = df_conn2.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "is_animated": "アニメ化",
        "ceno": "話数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_conn2.head()
マンガ雑誌名 アニメ化 話数 平均掲載位置 平均ページ数 カラー獲得率 作品数 text
0 週刊少年サンデー False 1 0.145375 42.392157 0.725490 255 1話目
1 週刊少年サンデー False 2 0.276159 26.811765 0.168627 255 2話目
2 週刊少年サンデー False 3 0.323798 20.584314 0.125490 255 3話目
3 週刊少年サンデー False 4 0.355677 18.094118 0.129412 255 4話目
4 週刊少年サンデー False 5 0.390275 17.494118 0.098039 255 5話目
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_conn2, DIR_OUT, "conn2")
DataFrame is saved as '../../../data/mix/output/vol2/06/assocs/conn2.csv'.
Hide code cell source
# create_connectedplotを使って連結散布図を作成
# x軸には'平均掲載位置'、y軸には'カラー獲得率'を設定し、マーカーの色には'話数'を設定
# 'アニメ化'の値によって列を分け、'マンガ雑誌名'によって行を分けてファセットを作成し、高さを調整
fig = create_connectedplot(
    df_conn2,
    x="平均掲載位置",
    y="カラー獲得率",
    color="話数",
    text="text",
    facet_col="アニメ化",
    facet_row="マンガ雑誌名",
    height=800,
)

# x軸とy軸の範囲を設定
fig.update_xaxes(range=[-0.05, 1.05])
fig.update_yaxes(range=[-0.05, 1.05])

# 各ファセットのアノテーション(タイトル)を更新し、不要なプレフィックスを削除
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成した図を表示する関数を呼び出し
show_fig(fig)