メディア展開データの内訳を見る#

準備#

Import#

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

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

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

# typingモジュールからListのインポート
# 型ヒントとして利用
from typing import Dict, List, Optional

# 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

変数#

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

# 分析結果の出力先ディレクトリのパス
DIR_OUT = (
    DIR_IN.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "props"
)
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
# 可視化に関する設定値を定義

# weekdayを曜日に変換するための辞書
WD2STR = {
    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 = 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 add_weekday_to_df(df: pd.DataFrame, col_date: str = "date") -> pd.DataFrame:
    """
    指定されたDataFrameに曜日の情報を追加する関数

    Parameters
    ----------
    df : pd.DataFrame
        曜日情報を追加する対象のDataFrame
    col_date : str, optional
        日付情報が含まれているカラムの名前、デフォルトは "date"

    Returns
    -------
    pd.DataFrame
        曜日情報が追加された新しいDataFrame
    """

    # 元のDataFrameをコピーして新しいDataFrameを作成
    df_new = df.copy()

    # 日付カラムを元に曜日の数値を計算して新しいカラムに追加
    df_new["weekday"] = pd.to_datetime(df_new[col_date]).dt.weekday

    # 数値の曜日を文字列に変換して新しいカラムに追加
    df_new["weekday_str"] = df_new["weekday"].apply(lambda x: WD2STR[x])

    return df_new
Hide code cell content
def create_mosaicplot(
    df: pd.DataFrame,
    x: str,
    y: str,
    color: str,
    width: str,
    text: str,
    color_discrete_sequence: List[str] = OKABE_ITO,
) -> go.Figure:
    """
    指定されたDataFrameを元にモザイクプロットを作成する関数

    Parameters
    ----------
    df : pd.DataFrame
        プロットに使用するデータが含まれるDataFrame
    x : str
        x軸に表示するデータのカラム名
    y : str
        y軸に表示するデータのカラム名
    color : str
        グループ分けの基準となるデータのカラム名
    width : str
        各バーの幅を表すデータのカラム名
    text : str
        各バーに表示するテキストのデータのカラム名
    color_discrete_sequence : List[str], optional
        使用する色のリスト デフォルトはOKABE_ITOのカラーパレット

    Returns
    -------
    go.Figure
        作成されたモザイクプロットのFigureオブジェクト
    """

    # 空のFigureオブジェクトを作成
    fig = go.Figure()

    # color列に登場するユニークな要素に対し、色をマッピング
    unique_keys = df[color].unique()
    color_map = {
        name: color for name, color in zip(unique_keys, color_discrete_sequence)
    }

    # color列のユニークな要素ごとにDataFrameをフィルタリング
    for i, name in enumerate(unique_keys):
        df_tmp = df[df[color] == name].reset_index(drop=True)
        # 幅をwidth列から抽出
        widths = df_tmp[width]

        # バーの位置を計算し、プロットに追加
        # 幅が変わるようxの値を調整
        fig.add_trace(
            go.Bar(
                name=name,
                x=df_tmp[width].cumsum() - widths,
                y=df_tmp[y],
                text=df_tmp[text],
                width=widths,
                offset=0,
                marker_color=color_map[name],
            )
        )

        # 最初の要素を用いて、X軸ラベルの設定値を作成
        if i == 0:
            # 各「棒」の中央に配置されるように座標を計算
            tickvals = df_tmp[width].cumsum() - df_tmp[width] / 2
            ticktext = df_tmp[x].unique()
            # x軸の表示範囲を決定するために利用
            x_max = df_tmp[width].sum()

    # x軸の目盛りの位置、テキスト、表示範囲を設定
    # 「棒」の太さの合計値を1としたとき、左右に0.1ずつ余白が残るように調整
    fig.update_xaxes(
        tickvals=tickvals, ticktext=ticktext, title=x, range=[-x_max * 0.1, x_max * 1.1]
    )

    # y軸のタイトルを設定
    fig.update_yaxes(title=y)

    # プロットのレイアウトを設定、凡例タイトルも指定
    fig.update_layout(barmode="stack", legend_title=color)

    return fig
Hide code cell content
def add_ratio_by_col(
    df: pd.DataFrame, col_val: str, col_by: str, col_sum: Optional[str] = None
) -> pd.DataFrame:
    """
    指定された列に基づいて、値の比率を計算し新たな列として追加

    Parameters
    ----------
    df : pd.DataFrame
        比率を計算するためのデータフレーム
    col_val : str
        比率を計算するための値を含む列の名前
    col_by : str
        比率をグループ化する基準となる列の名前
    col_sum : Optional[str], default None
        グループごとの合計値を格納する列の名前。未指定の場合は自動で生成されます。

    Returns
    -------
    pd.DataFrame
        元のデータフレームに比率を表す新しい列「ratio」を追加したデータフレーム
    """
    df_out = df.copy()

    # col_sumが未指定の場合は自動で列名を生成
    if col_sum is None:
        col_sum = f"{col_by}_total"

    # 指定されたグループ列に基づいて値の合計を計算し、一時的なデータフレームを作成
    df_tmp = df_out.groupby(col_by)[col_val].sum().reset_index(name=col_sum)

    # 一時的なデータフレームを元のデータフレームにマージし、各グループの合計値を追加
    df_out = pd.merge(df_out, df_tmp, how="left", on=col_by)

    # 指定された値の列を、そのグループの合計値で割り、新しい列「ratio」にその比率を格納
    df_out["ratio"] = df_out[col_val] / df_out[col_sum]

    return df_out
Hide code cell content
def add_text_of_ratio(df: pd.DataFrame, col_ratio: str) -> pd.DataFrame:
    """
    指定された比率の列を文字列形式に変換し、新たな列として追加

    比率は小数点以下2桁までの形式で文字列に変換

    Parameters
    ----------
    df : pd.DataFrame
        比率の文字列化を行う元のデータフレーム
    col_ratio : str
        比率を含む列の名前

    Returns
    -------
    pd.DataFrame
        元のデータフレームに、比率を表す文字列を含む新しい列「text」を追加したデータフレーム
    """
    df_out = df.copy()
    # 指定された比率の列を小数点以下2桁の文字列形式に変換して、新しい列"text"に格納
    df_out["text"] = df_out[col_ratio].apply(lambda x: f"{x:.2f}")
    return df_out
Hide code cell content
def add_weekday(df: pd.DataFrame, col_date: str = "date") -> pd.DataFrame:
    """
    指定された日付列に基づいて、曜日情報を整数と文字列の形式で新たな列として追加

    Parameters
    ----------
    df : pd.DataFrame
        曜日情報を追加する元のデータフレーム
    col_date : str, optional
        日付情報を含む列の名前、デフォルトは'date'

    Returns
    -------
    pd.DataFrame
        元のデータフレームに、曜日情報を表す2つの新しい列「weekday」と
        「weekday_str」を追加したデータフレーム
    """
    df_new = df.copy()
    # 指定された日付列をdatetime型に変換し、曜日を整数で表す新しい列'weekday'を追加
    df_new["weekday"] = pd.to_datetime(df_new[col_date]).dt.weekday
    # 'weekday'列の整数値を曜日の文字列に変換し、新しい列'weekday_str'に追加
    # WD2STRは曜日の整数値をキー、曜日の名前を値とする辞書である必要がある
    df_new["weekday_str"] = df_new["weekday"].apply(lambda x: WD2STR[x])
    return df_new
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
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データフレームを日付とceidで昇順に並び替え、新しいインデックスを割り当て
df_ce_ac = df_ce_ac.sort_values(["date", "ceid"], ignore_index=True)

# ccidで重複を削除し、それぞれのccidについて最初に記録された行のみを保持
df_cc_ac = df_ce_ac.drop_duplicates("ccid", ignore_index=True)

# n_ceがmin_nce以上、かつfirst_date_ccの年が1990年以降の行のみを選択
df_cc_ac = df_cc_ac[
    (df_cc_ac["n_ce"] >= min_nce)
    & (pd.to_datetime(df_cc_ac["first_date_cc"]).dt.year >= 1990)
].reset_index(drop=True)

# "acid"列に欠損値がない場合、つまりアニメーションが存在する場合にTrueを設定し、
# 新しい列"is_animated"をデータフレームに追加
df_cc_ac["is_animated"] = ~df_cc_ac["acid"].isna()
Hide code cell content
# df_cc_acデータフレームをアニメ化されているかどうか(is_animated)でグループ化し、
# それぞれのグループにおけるユニークなマンガ作品(ccid)の数を計算
df_pie = df_cc_ac.groupby("is_animated")["ccid"].nunique().reset_index(name="n_cc")

# 得られたデータフレームの列名をより分かりやすい名称に変更
df_pie = df_pie.rename(columns={"is_animated": "アニメ化", "n_cc": "マンガ作品数"})
Hide code cell content
# 可視化対象のDataFrameを確認
df_pie.head()
アニメ化 マンガ作品数
0 False 1208
1 True 129
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_pie, DIR_OUT, "pie")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/pie.csv'.
Hide code cell source
# Plotly Expressを使用して、df_pieデータフレームから円グラフを作成
# 'マンガ作品数'を円グラフの各セクションの大きさに、'アニメ化'を各セクションの名前に使用
fig = px.pie(
    df_pie, values="マンガ作品数", names="アニメ化", color_discrete_sequence=OKABE_ITO
)

# レイアウトを更新して凡例のタイトルを'アニメ化'に設定
fig.update_layout(legend={"title": "アニメ化"})

# 作成した図を表示する関数を呼び出し
show_fig(fig)
Hide code cell content
# df_cc_acデータフレームをマンガ雑誌名とアニメ化されているかどうかでグループ化し、
# それぞれのグループにおけるユニークなマンガ作品(ccid)の数を計算
df_pie2 = (
    df_cc_ac.groupby(["mcname", "is_animated"])["ccid"]
    .nunique()
    .reset_index(name="n_cc")
)

# 得られたデータフレームの列名をより分かりやすい名称に変更
df_pie2 = df_pie2.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "is_animated": "アニメ化",
        "n_cc": "マンガ作品数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_pie2.head()
マンガ雑誌名 アニメ化 マンガ作品数
0 週刊少年サンデー False 255
1 週刊少年サンデー True 32
2 週刊少年ジャンプ False 288
3 週刊少年ジャンプ True 49
4 週刊少年チャンピオン False 381
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_pie2, DIR_OUT, "pie2")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/pie2.csv'.
Hide code cell source
# 'マンガ作品数'を円グラフの各セクションの大きさに、'アニメ化'を各セクションの名前に使用
# 'facet_col'によって、マンガ雑誌名ごとに異なる円グラフを生成
fig = px.pie(
    df_pie2,
    values="マンガ作品数",
    names="アニメ化",
    facet_col="マンガ雑誌名",
    color_discrete_sequence=OKABE_ITO,
)

# レイアウトを更新して凡例のタイトルを'アニメ化'に設定
fig.update_layout(legend={"title": "アニメ化"})

# 各円グラフのタイトル(マンガ雑誌名)を、"マンガ雑誌名=値"から"値"のみに短縮
# これにより、グラフ上の各マンガ雑誌名の表示が簡潔になる
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成した図を表示
show_fig(fig)
Hide code cell content
# df_pie2データフレームのコピーを作成し、df_pie3として保存
df_pie3 = df_pie2.copy()

# 'マンガ雑誌名'ごとにマンガ作品数の合計を計算し、辞書として格納
# これにより、各マンガ雑誌で全体のマンガ作品数が分かる
mcname2ncc_total = df_pie3.groupby("マンガ雑誌名")["マンガ作品数"].sum().to_dict()

# 'アニメ化'がTrueのレコードをフィルタリングし、
# 各マンガ雑誌名の最初のマンガ作品数を計算して辞書として格納
# これにより、各マンガ雑誌でアニメ化されたマンガ作品数が分かる
mcname2ncc_anime = (
    df_pie3[df_pie3["アニメ化"]]
    .groupby("マンガ雑誌名")["マンガ作品数"]
    .first()
    .to_dict()
)

# 各マンガ雑誌名ごとにアニメ化率を計算し、辞書として格納
# アニメ化率はアニメ化されたマンガ作品数を全体のマンガ作品数で割ったもの
mcname2ratio = {k: v / mcname2ncc_total[k] for k, v in mcname2ncc_anime.items()}

# 'マンガ雑誌名'ごとにアニメ化率を'アニメ化率'列としてデータフレームに追加
df_pie3["アニメ化率"] = df_pie3["マンガ雑誌名"].map(mcname2ratio)

# データフレームを'アニメ化率'と'アニメ化'で昇順に並び替え、インデックスをリセット
# これにより、アニメ化率が低い雑誌から高い雑誌へと順に並ぶ
df_pie3 = df_pie3.sort_values(["アニメ化率", "アニメ化"], ignore_index=True)
Hide code cell content
# 可視化対象のDataFrameを確認
df_pie3.head()
マンガ雑誌名 アニメ化 マンガ作品数 アニメ化率
0 週刊少年チャンピオン False 381 0.028061
1 週刊少年チャンピオン True 11 0.028061
2 週刊少年サンデー False 255 0.111498
3 週刊少年サンデー True 32 0.111498
4 週刊少年マガジン False 284 0.115265
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_pie3, DIR_OUT, "pie3")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/pie3.csv'.
Hide code cell source
# 'マンガ作品数'を円グラフの各セクションの大きさに、'アニメ化'を各セクションの名前に使用
# 'facet_col'によって、マンガ雑誌名ごとに異なる円グラフを生成
fig = px.pie(
    df_pie3,
    values="マンガ作品数",
    names="アニメ化",
    facet_col="マンガ雑誌名",
    color_discrete_sequence=OKABE_ITO,
)

# レイアウトを更新して凡例のタイトルを'アニメ化'に設定
fig.update_layout(legend={"title": "アニメ化"})

# 各円グラフのタイトル(マンガ雑誌名)を、"マンガ雑誌名=値"から"値"のみに短縮
# これにより、グラフ上の各マンガ雑誌名の表示が簡潔になる
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成した図を表示
show_fig(fig)

棒グラフ#

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

# 各マンガ雑誌名ごとに、マンガ作品数の比率を計算し、新たな列として追加
df_bar = add_ratio_by_col(df_bar, "マンガ作品数", "マンガ雑誌名")

# 計算した比率を文字列形式で整形し、「text」列として追加
df_bar = add_text_of_ratio(df_bar, "ratio")

# 「ratio」列の名前を「比率」に変更
# これにより、データフレームの列名がより直感的に理解しやすくなる
df_bar = df_bar.rename(columns={"ratio": "比率"})

# アニメ化率列を追加し、これに基づきソート
df_bar["アニメ化率"] = df_bar["マンガ雑誌名"].map(mcname2ratio)
df_bar = df_bar.sort_values(
    ["アニメ化率", "アニメ化"], ascending=[True, False], ignore_index=True
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_bar.head()
マンガ雑誌名 アニメ化 マンガ作品数 マンガ雑誌名_total 比率 text アニメ化率
0 週刊少年チャンピオン True 11 392 0.028061 0.03 0.028061
1 週刊少年チャンピオン False 381 392 0.971939 0.97 0.028061
2 週刊少年サンデー True 32 287 0.111498 0.11 0.111498
3 週刊少年サンデー False 255 287 0.888502 0.89 0.111498
4 週刊少年マガジン True 37 321 0.115265 0.12 0.115265
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar, DIR_OUT, "bar")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/bar.csv'.
Hide code cell source
# Plotly Expressを使用して、df_barデータフレームから棒グラフを作成
# 'マンガ雑誌名'をx軸に、'比率'をy軸に設定し、'アニメ化'によって色分け
# 'text'列の値を棒グラフ上にテキストとして表示
# 'barmode'で'stack'を指定することで積上げ棒グラフ化
# 色の配列をOKABE_ITOの最初の2色を逆順(アニメ化実績ありをオレンジにするため)で指定
fig = px.bar(
    df_bar,
    x="マンガ雑誌名",
    y="比率",
    barmode="stack",
    color="アニメ化",
    text="text",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成した図を表示する関数を呼び出し
show_fig(fig)
Hide code cell source
# Plotly Expressを使用して、df_barデータフレームから棒グラフを作成
# 'マンガ雑誌名'をx軸に、'マンガ作品数'をy軸に設定し、'アニメ化'によって色分け
# 'マンガ作品数'列の値を棒グラフ上にテキストとして表示
# 'barmode'で'stack'を指定することで積上げ棒グラフ化
# 色の配列をOKABE_ITOの最初の2色を逆順(アニメ化実績ありをオレンジにするため)で指定
fig = px.bar(
    df_bar,
    x="マンガ雑誌名",
    y="マンガ作品数",
    barmode="stack",
    color="アニメ化",
    text="マンガ作品数",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成した図を表示する関数を呼び出し
show_fig(fig)
Hide code cell source
# Plotly Expressを使用して、df_barデータフレームから棒グラフを作成
# 事前にdf_barを'マンガ雑誌名_total'で並び替えておくことで視認性を高める
# 'マンガ雑誌名'をx軸に、'マンガ作品数'をy軸に設定し、'アニメ化'によって色分け
# 'マンガ作品数'列の値を棒グラフ上にテキストとして表示
# 'barmode'で'stack'を指定することで積上げ棒グラフ化
# 色の配列をOKABE_ITOの最初の2色を逆順(アニメ化実績ありをオレンジにするため)で指定
fig = px.bar(
    df_bar.sort_values("マンガ雑誌名_total"),
    x="マンガ雑誌名",
    y="マンガ作品数",
    barmode="stack",
    color="アニメ化",
    text="マンガ作品数",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

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

モザイクプロット#

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

# 'アニメ化'列のデータ型を文字列型に変換
# (create_mosaicplotではブール値の列を扱えないため)
df_mosaic["アニメ化"] = df_mosaic["アニメ化"].astype(str)
Hide code cell content
# 可視化対象のDataFrameを表示
df_mosaic.head()
マンガ雑誌名 アニメ化 マンガ作品数 マンガ雑誌名_total 比率 text アニメ化率
0 週刊少年チャンピオン True 11 392 0.028061 0.03 0.028061
1 週刊少年チャンピオン False 381 392 0.971939 0.97 0.028061
2 週刊少年サンデー True 32 287 0.111498 0.11 0.111498
3 週刊少年サンデー False 255 287 0.888502 0.89 0.111498
4 週刊少年マガジン True 37 321 0.115265 0.12 0.115265
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_mosaic, DIR_OUT, "mosaic")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/mosaic.csv'.
Hide code cell source
# create_mosaicplot関数を使用して、df_mosaicデータフレームからモザイクプロットを作成
# モザイクプロットは、'マンガ雑誌名'を基準に、'比率'に基づいて各セグメントの大きさを決定
# 'アニメ化'によって色分けし、各セグメントの幅は'マンガ雑誌名_total'に基づく
# 'text'列の値を各セグメント上にテキストとして表示し、色の配列はOKABE_ITOの最初の2色を逆順で指定
fig = create_mosaicplot(
    df_mosaic,
    x="マンガ雑誌名",
    y="比率",
    color="アニメ化",
    width="マンガ雑誌名_total",
    text="text",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成したモザイクプロットを表示する関数を呼び出し
show_fig(fig)

積上げ密度プロット[1]#

Hide code cell content
# `first_date_cc`列を日付型に変換して、その年だけを取り出し`first_year_cc`列として追加
df_cc_ac["first_year_cc"] = pd.to_datetime(df_cc_ac["first_date_cc"]).dt.year

# `first_year_cc`と`is_animated`でグループ化し
# ユニークな`ccid`の数をカウントして新しいデータフレームを作成
df_area = (
    df_cc_ac.groupby(["first_year_cc", "is_animated"])["ccid"]
    .nunique()
    .reset_index(name="マンガ作品数")
)

# `first_year_cc`と`is_animated`でソート、アニメ化されたものを優先して表示
df_area = df_area.sort_values(
    by=["first_year_cc", "is_animated"], ascending=[True, False], ignore_index=True
)

# `is_animated`列のデータタイプを文字列に変更
df_area["is_animated"] = df_area["is_animated"].astype(str)

# 各年ごとのマンガ作品数の割合を計算して列に追加
df_area = add_ratio_by_col(df_area, "マンガ作品数", "first_year_cc")

# 列名をわかりやすい名前に変更
df_area = df_area.rename(
    columns={
        "first_year_cc": "連載開始年",
        "is_animated": "アニメ化",
        "ratio": "比率",
        "n_cc": "マンガ作品数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_area.head()
連載開始年 アニメ化 マンガ作品数 first_year_cc_total 比率
0 1990 True 4 50 0.080000
1 1990 False 46 50 0.920000
2 1991 True 3 51 0.058824
3 1991 False 48 51 0.941176
4 1992 True 1 45 0.022222
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_area, DIR_OUT, "area")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/area.csv'.
Hide code cell source
# 積上げ密度グラフを作成
# x軸には連載開始年、y軸には比率、色分けにはアニメ化の有無を使用
# カラースキームとしてOKABE_ITOパレットの最初の2色を逆順で使用
# (逆順にしたのは、アニメ化実績のある作品をオレンジ色にするため)
# ホバー時に表示するデータにマンガ作品数を追加
fig = px.area(
    df_area,
    x="連載開始年",
    y="比率",
    color="アニメ化",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
    hover_data=["マンガ作品数"],
)

# ホバー時の挙動をとして'x unified'を設定
fig.update_layout(hovermode="x unified")

# グラフを表示
show_fig(fig)
Hide code cell content
# 2003年に連載が開始され、かつアニメ化された作品をフィルタリングし、特定の列のみ表示
df_cc_ac[(df_cc_ac["first_year_cc"] == 2003) & (df_cc_ac["is_animated"])][
    ["mcname", "ccname", "acname"]
]
mcname ccname acname
623 週刊少年サンデー MAR メル MÄR メルヘヴン MÄRCHEN AWAKENS ROMANCE
629 週刊少年マガジン 魔法先生 ネギま! MAGISTER NEGI MAGI 魔法先生 ネギま!
637 週刊少年サンデー 結界師 結界師
640 週刊少年マガジン ツバサ ~RESERVoir CHRoNiCLE~ ツバサ・クロニクル 年代記[第1期]
644 週刊少年ジャンプ 武装錬金 武装錬金
652 週刊少年ジャンプ DEATH NOTE DEATH NOTE
663 週刊少年ジャンプ 家庭教師ヒットマンREBORN! 家庭教師* ヒットマン REBORN! *[かてきょー]
664 週刊少年マガジン 涼風 涼風[すずか]
Hide code cell source
# 積上げ密度グラフを作成
# x軸には連載開始年、y軸にはマンガ作品数の割合、色分けにはアニメ化の有無を使用
# カラースキームとしてOKABE_ITOパレットの最初の2色を逆順で使用
# (逆順にしたのは、アニメ化実績のある作品をオレンジ色にするため)
# ホバー時に表示するデータに比率を追加
fig = px.area(
    df_area,
    x="連載開始年",
    y="マンガ作品数",
    color="アニメ化",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
    hover_data=["比率"],
)

# ホバー時の挙動をとして'x unified'を設定
fig.update_layout(hovermode="x unified")

# グラフを表示
show_fig(fig)
Hide code cell content
# `mcname`, `first_year_cc`, `is_animated`でグループ化し、
# 各グループ内のユニークな`ccid`の数をカウントし、マンガ作品数列として追加
df_area2 = (
    df_cc_ac.groupby(["mcname", "first_year_cc", "is_animated"])["ccid"]
    .nunique()
    .reset_index(name="マンガ作品数")
)

# 結果を`mcname`, `first_year_cc`, `is_animated`の順にソート
# `mcname`と`first_year_cc`は昇順、`is_animated`は降順
df_area2 = df_area2.sort_values(
    by=["mcname", "first_year_cc", "is_animated"],
    ascending=[True, True, False],
    ignore_index=True,
)

# `is_animated`列のデータタイプをブール型から文字列型に変更
df_area2["is_animated"] = df_area2["is_animated"].astype(str)

# `mcname`と`first_year_cc`ごとに`n_cc`(マンガ作品数)の比率を計算し、新しい列として追加
df_area2 = add_ratio_by_col(
    df_area2, "マンガ作品数", ["mcname", "first_year_cc"], col_sum="mcname_fyear_total"
)

# 列名をよりわかりやすく変更
df_area2 = df_area2.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "first_year_cc": "連載開始年",
        "is_animated": "アニメ化",
        "n_cc": "マンガ作品数",
        "ratio": "比率",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_area2.head()
マンガ雑誌名 連載開始年 アニメ化 マンガ作品数 mcname_fyear_total 比率
0 週刊少年サンデー 1990 True 1 12 0.083333
1 週刊少年サンデー 1990 False 11 12 0.916667
2 週刊少年サンデー 1991 True 1 10 0.100000
3 週刊少年サンデー 1991 False 9 10 0.900000
4 週刊少年サンデー 1992 False 14 14 1.000000
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_area2, DIR_OUT, "area2")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/area2.csv'.
Hide code cell source
# 積上げ密度プロットを作成
# x軸には連載開始年、y軸にはマンガ作品数、色分けにはアニメ化の有無を使用
# マンガ雑誌名ごとに異なるファセットを作成、ただし各ファセットは縦に並べる
# カラースキームにはOKABE_ITOの最初の2色を逆順で使用
fig = px.area(
    df_area2,
    x="連載開始年",
    y="マンガ作品数",
    color="アニメ化",
    facet_col="マンガ雑誌名",
    facet_col_wrap=1,
    color_discrete_sequence=OKABE_ITO[:2][::-1],
    height=600,
)

# ホバー時の挙動を設定。'x unified'は同じx値を持つデータポイントで情報を統一して表示
fig.update_layout(hovermode="x unified")

# 各ファセットのタイトル(マンガ雑誌名)を編集し、雑誌名のみを表示
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# グラフを表示
show_fig(fig)

ツリーマップ#

Hide code cell content
# アニメ化実績のあるレコードのみを抽出
df_tree = df_cc_ac[df_cc_ac["is_animated"]].reset_index(drop=True)

# 可視化用に列名を変更
cols_tree = {"mcname": "マンガ雑誌名", "ccname": "マンガ作品名", "n_ae": "アニメ各話数"}
df_tree = format_cols(df_tree, cols_tree)
Hide code cell content
# 可視化対象のDataFrameを確認
df_tree.head()
マンガ雑誌名 マンガ作品名 アニメ各話数
0 週刊少年サンデー うしおととら 26.0
1 週刊少年マガジン シュート! 58.0
2 週刊少年ジャンプ SLAM DUNK 101.0
3 週刊少年ジャンプ 幽☆遊☆白書 112.0
4 週刊少年サンデー ゴーストスイーパー美神 極楽大作戦!! 45.0
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_tree, DIR_OUT, "tree")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/tree.csv'.
Hide code cell source
# ツリーマップを作成
# 連載開始が1990年以降のマンガ作品を原作とするアニメ作品をテーマに設定
# 最上位は固定のテキスト、次にマンガ雑誌名、最後にマンガ作品名を指定し、サイズを`n_ae`で定義
# カラースキームにはOKABE_ITOパレットを使用
fig = px.treemap(
    df_tree,
    path=[
        px.Constant("1990年以降連載開始のマンガ作品を原作とするアニメ作品"),
        "マンガ雑誌名",
        "マンガ作品名",
    ],
    values="アニメ各話数",
    color_discrete_sequence=OKABE_ITO,
)

# 背景色を薄いグレーに設定
fig.update_traces(root_color="lightgrey")

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

パラレルセットグラフ#

Hide code cell content
# 各マンガ作品の各話数数(n_ce)の閾値リストを作成
# 最小値(min_nce)、四分位数(25%、50%、75%の値)、最大値+1をリストに含める
ths_ce = (
    [min_nce]
    + list(df_cc_ac["n_ce"].quantile([0.25, 0.5, 0.75]).astype(int))
    + [df_cc_ac["n_ce"].max() + 1]
)

# マンガ作品IDからグループ名とグループIDをマッピングするための辞書を初期化
ccid2gname = {}
ccid2gid = {}

# 閾値リストを用いてマンガ作品を異なるグループに分類
for i in range(len(ths_ce) - 1):
    lower = ths_ce[i]  # 現在の区間の下限値
    upper = ths_ce[i + 1]  # 現在の区間の上限値

    # n_ceが現在の閾値区間に含まれるマンガ作品をフィルタリング
    df_q = df_cc_ac[(df_cc_ac["n_ce"] >= lower) & (df_cc_ac["n_ce"] < upper)]

    # 現在の区間に基づいてグループ名を定義(例: "8-15話")
    gname = f"{lower}-{upper-1}話"
    # フィルタリングされたマンガ作品のIDごとにグループ名をマッピング
    ccid2gname.update({ccid: gname for ccid in df_q["ccid"]})
    # フィルタリングされたマンガ作品のIDごとにグループIDをマッピング
    ccid2gid.update({ccid: i for ccid in df_q["ccid"]})
Hide code cell content
# df_cc_acデータフレームのコピーを作成してdf_parに格納
df_par = df_cc_ac.copy()

# 'ccid'に基づいてマンガ作品を特定のグループ名にマッピングし、新しい列'gname'に格納
df_par["gname"] = df_par["ccid"].map(ccid2gname)
# 'ccid'に基づいてマンガ作品を特定のグループIDにマッピングし、新しい列'gid'に格納
df_par["gid"] = df_par["ccid"].map(ccid2gid)

# 'is_animated'列を整数型に変換し、新しい列'is_animated_int'に格納
# アニメ化されているかどうかの真偽値を整数値に変換する
# (パラレルセットグラフでは、色分け対象の列としてカテゴリカル値を指定できないため)
df_par["is_animated_int"] = df_par["is_animated"].astype(int)

# 'is_animated'列を文字列型に変換し、後の処理や表示を統一しやすくする
df_par["is_animated"] = df_par["is_animated"].astype(str)

# 'mcname'(マンガ雑誌名)、'is_animated'(アニメ化されているか)、'gid'(グループID)に基づいて
# データフレームを並び替え、新しいインデックスを割り当て
df_par = df_par.sort_values(["mcname", "is_animated", "gid"], ignore_index=True)

# データフレームの列名をより分かりやすい名称に変更
cols_par = {
    "ccid": "マンガ作品ID",
    "is_animated": "アニメ化",
    "is_animated_int": "アニメ化有無ID",
    "gname": "合計話数",
    "gid": "グループID",
    "mcname": "マンガ雑誌名",
}
df_par = format_cols(df_par, cols_par)
Hide code cell content
# 可視化対象のDataFrameを確認
df_par.head()
マンガ作品ID アニメ化 アニメ化有無ID 合計話数 グループID マンガ雑誌名
0 C92355 False 0 8-17話 0 週刊少年サンデー
1 C93126 False 0 8-17話 0 週刊少年サンデー
2 C91698 False 0 8-17話 0 週刊少年サンデー
3 C93467 False 0 8-17話 0 週刊少年サンデー
4 C93829 False 0 8-17話 0 週刊少年サンデー
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_par, DIR_OUT, "par")
DataFrame is saved as '../../../data/mix/output/vol2/06/props/par.csv'.
Hide code cell source
# Plotly Expressを使用して、df_parデータフレームからパラレルセットグラフを作成
# 'アニメ化'、'合計話数'、'マンガ雑誌名'を軸として使用
# 'is_animated_int'に基づいて、OKABE_ITOスケールで色分け
fig = px.parallel_categories(
    df_par,
    dimensions=["アニメ化", "合計話数", "マンガ雑誌名"],
    color="アニメ化有無ID",
    color_continuous_scale=OKABE_ITO[:2],
)

# 色のスケールバーを非表示に設定
fig.update_coloraxes(showscale=False)

# 作成したパラレルセットグラフを表示する関数を呼び出し
show_fig(fig)
Hide code cell source
# Plotly Expressを使用して、df_parデータフレームからパラレルセットグラフを作成
# '合計話数'、'アニメ化'、'マンガ雑誌名'を軸として使用
# 'グループID'に基づいて、px.colors.diverging.Portlandで色分け
fig = px.parallel_categories(
    df_par,
    dimensions=["合計話数", "アニメ化", "マンガ雑誌名"],
    color="グループID",
    color_continuous_scale=px.colors.diverging.Portland,
)

# 色のスケールバーを非表示に設定
fig.update_coloraxes(showscale=False)

# 作成したパラレルセットグラフを表示する関数を呼び出し
show_fig(fig)