マンガデータの分布を見る#
準備#
Import#
Show code cell content
# warningsモジュールのインポート
import warnings
# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Show code cell content
# 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.figure_factoryのインポート
# 高度なプロットとデータ可視化のためのユーティリティ
# ffという名前で参照可能
import plotly.figure_factory as ff
# 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
変数#
Show code cell content
# マンガデータが保存されているディレクトリのパス
DIR_IN = Path("../../../data/cm/input")
# 分析結果の出力先ディレクトリのパス
DIR_OUT = (
DIR_IN.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "dists"
)
Show code cell content
# 読み込み対象ファイル名の定義
# マンガ各話に関するファイル
FN_CE = "cm_ce.csv"
# マンガ作品と原作者の対応関係に関するファイル
FN_CC_CRT = "cm_cc_crt.csv"
Show code cell content
# plotlyの描画設定の定義
# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"
関数#
Show 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)
Show code cell content
def create_distplot(
df: pd.DataFrame,
x: str,
color: str = None,
show_hist: bool = False,
show_rug: bool = False,
**kwargs: Any
) -> Figure:
"""
データフレームから密度プロットとヒストグラムを作成する
Parameters
----------
df : pd.DataFrame
プロットするデータを含むデータフレーム
x : str
密度プロットの描画対象とするカラム名
color : str, optional
データを分割する基準とするカラム名、指定しない場合はx列の全データを用いる
show_hist : bool, optional
ヒストグラムを表示するか否か、デフォルトはFalse
show_rug : bool, optional
ラグプロットを表示するか否か、デフォルトはFalse
**kwargs
ff.create_distplotに渡すその他のキーワード引数
Returns
-------
Figure
作成されたプロットのFigureオブジェクト
"""
if color:
# colorカラムの値でデータをグループ分け
grouped = df.groupby(color)
# 各グループのxカラムのデータをリストに格納、可視化用に逆順に並び替え
hist_data = [group[x].values for _, group in grouped][::-1]
# 各グループの名前(colorカラムの値)をラベルとしてリストに格納、可視化用に逆順に並び替え
labels = [str(name) for name, _ in grouped][::-1]
# 密度プロットとヒストグラムを作成
fig = ff.create_distplot(
hist_data, labels, show_hist=show_hist, show_rug=show_rug, **kwargs
)
else:
# colorが指定されていない場合はx列の全データを用いる
hist_data = [df[x].values]
# 密度プロットを作成(ラベルはxを指定)
fig = ff.create_distplot(
hist_data,
group_labels=[x],
show_hist=show_hist,
show_rug=show_rug,
**kwargs
)
# x軸のタイトルをxに変更
fig.update_xaxes(title=x)
# y軸のタイトルを"確率密度"に変更
fig.update_yaxes(title="確率密度")
# 作成されたプロットを返す
return fig
Show code cell content
def create_split_violin_plot(
df: pd.DataFrame, x: str, y: str, split: str, **kwargs
) -> Figure:
"""
DataFrameからsplit violin plotを作成する関数
Parameters
----------
df : pandas.DataFrame
データを含むDataFrame
x : str
X軸に使用するカラム名
y : str
Y軸に使用するカラム名
split : str
バイオリンを分割する際に使用するカラム名、ブール値である必要がある
**kwargs : dict
go.Violinに渡す追加のキーワード引数
Returns
-------
fig : plotly.graph_objects.Figure
生成されたバイオリンプロットの図
"""
# 新しい図オブジェクトを作成
fig = go.Figure()
# Trueのデータでバイオリンプロットを作成
# ここでX軸とY軸にデータを割り当て、プロットの色やスタイルを設定
fig.add_trace(
go.Violin(
x=df[x][df[split]],
y=df[y][df[split]],
legendgroup="True", # 凡例グループを設定
scalegroup="True", # スケールグループを設定
name="True", # 凡例名を設定
side="negative", # バイオリンの配置を左側に設定
line_color="blue", # ラインの色を青に設定
points=False, # ポイントを表示しないように設定
**kwargs
)
)
# Falseのデータでバイオリンプロットを作成
# Trueのときと同様にデータを割り当て、プロットの色やスタイルを設定
fig.add_trace(
go.Violin(
x=df[x][~df[split]],
y=df[y][~df[split]],
legendgroup="False", # 凡例グループを設定
scalegroup="False", # スケールグループを設定
name="False", # 凡例名を設定
side="positive", # バイオリンの配置を右側に設定
line_color="orange", # ラインの色をオレンジに設定
points=False, # ポイントを表示しないように設定
**kwargs
)
)
# プロットのスケールモードを"count"に設定し、中央値の線を表示
fig.update_traces(scalemode="count", meanline_visible=True)
# 図のレイアウトを更新
# 軸のタイトルと凡例のタイトルを設定し、バイオリン間のギャップとモードを設定
fig.update_layout(
xaxis_title=x, # X軸のタイトルを設定
yaxis_title=y, # Y軸のタイトルを設定
legend_title=split, # 凡例のタイトルを設定
violingap=0, # バイオリン間のギャップを0に設定
violinmode="overlay", # バイオリンモードを"overlay"に設定
)
return fig
Show 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
Show 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}'.")
可視化例#
Show code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ce = pd.read_csv(DIR_IN / FN_CE)
df_cc_crt = pd.read_csv(DIR_IN / FN_CC_CRT)
Show code cell content
# 企画系のマンガを除外するため、5ページ以上のマンガ各話を分析とする
df_ce = df_ce[df_ce["pages"] >= 5].reset_index(drop=True)
ヒストグラム#
Show code cell content
# 連載マンガ作品として扱う最小のマンガ各話数を、マンガデータの基礎分析を踏まえ設定
min_nce = 8
# 'ccid'でグループ化し、各ccnameについてユニークなceidの数をカウント
df_cc_nce = df_ce.groupby(["ccid"])["ceid"].nunique().reset_index(name="n_ce")
# n_ceの値がmin_nce以上の行だけを保持
df_cc_nce = df_cc_nce[df_cc_nce["n_ce"] >= min_nce].reset_index(drop=True)
# n_ceの値でデータフレームを昇順にソート
df_cc_nce = df_cc_nce.sort_values("n_ce", ignore_index=True)
Show code cell content
# df_ceを日付とceidでソートし、各ccidについて最初のmin_nce件のデータを取り出す
df_hist = (
df_ce.sort_values(["date", "ceid"], ignore_index=True).groupby("ccid").head(min_nce)
)
# df_cc_nceのccid列に含まれるccidの行だけを保持
df_hist = df_hist[df_hist["ccid"].isin(df_cc_nce["ccid"].unique())]
# 可視化用に保持するカラム一覧
cols2rename = {
"page_start_position": "掲載位置",
"ceid": "ceid",
"ccid": "ccid",
"mcname": "mcname",
"date": "date",
}
# 可視化用にカラム名を変更
df_hist = format_cols(df_hist, cols2rename)
Show code cell content
# 可視化対象のDataFrameを確認
df_hist.head()
| 掲載位置 | ceid | ccid | mcname | date | |
|---|---|---|---|---|---|
| 0 | 0.051724 | CE117459 | C94272 | 週刊少年チャンピオン | 1970-07-27 |
| 1 | 0.165517 | CE117460 | C94289 | 週刊少年チャンピオン | 1970-07-27 |
| 2 | 0.231034 | CE117461 | C94447 | 週刊少年チャンピオン | 1970-07-27 |
| 3 | 0.296552 | CE117462 | C94949 | 週刊少年チャンピオン | 1970-07-27 |
| 4 | 0.451724 | CE117466 | C95858 | 週刊少年チャンピオン | 1970-07-27 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_hist, DIR_OUT, "hist")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/hist.csv'.
Show code cell source
# df_histを用いて「掲載位置」に関するヒストグラムを作成し、y軸のタイトルを「各話数」に設定
fig = px.histogram(df_hist, x="掲載位置").update_yaxes(title="各話数")
# ヒストグラムを表示
show_fig(fig)
Show code cell source
# df_histのうち第一話のみを抽出して「掲載位置」に関するヒストグラムを作成
# y軸のタイトルを「各話数」に設定
fig = px.histogram(df_hist.groupby("ccid").head(1), x="掲載位置").update_yaxes(
title="各話数"
)
# ヒストグラムを表示
show_fig(fig)
Show code cell content
# min_nce、四分位数、最大値+1を使ってデータの範囲(threshold)を示すリストths_ceを作成
# 後に登場するfor文をきれいに書くために、min_nceと、n_ceの最大値+1を追加
ths_ce = (
[min_nce] # min_nceをリストの最初に追加
+ list(df_cc_nce["n_ce"].quantile([0.25, 0.5, 0.75]).astype(int)) # 四分位数を追加
+ [df_cc_nce["n_ce"].max() + 1] # 最大値+1をリストの最後に追加
)
# ccidをグループ名にマップするための辞書を初期化
ccid2gname = {}
# ths_ceリスト内の閾値ペアをループして処理
for i in range(len(ths_ce) - 1):
# 現在の閾値を下限、次の閾値を上限として設定
lower = ths_ce[i]
upper = ths_ce[i + 1]
# n_ceが現在の閾値範囲内にある行だけを抽出してdf_qに保存
df_q = df_cc_nce[(df_cc_nce["n_ce"] >= lower) & (df_cc_nce["n_ce"] < upper)]
# ccidとグループ名をマッピングする辞書を作成
gname = f"第{i+1}群(合計話数:{lower}-{upper-1}話)"
ccid2gname.update({ccid: gname for ccid in df_q["ccid"]})
Show code cell content
# 可視化用に新たなDataFrameを作成
df_hist2 = df_hist.copy()
# df_hist2のccnameに基づきにグループ名をマッピング
df_hist2["gname"] = df_hist2["ccid"].map(ccid2gname)
# gnameとmcnameでdf_hist2をソート
df_hist2 = df_hist2.sort_values(["gname", "mcname"], ignore_index=True)
# 可視化用にカラム名を変更
df_hist2 = df_hist2.rename(columns={"gname": "グループ名"})
Show code cell content
# 可視化対象のDataFrameを確認
df_hist2.head()
| 掲載位置 | ceid | ccid | mcname | date | グループ名 | |
|---|---|---|---|---|---|---|
| 0 | 0.373239 | CE155567 | C93019 | 週刊少年サンデー | 1970-08-02 | 第1群(合計話数:8-16話) |
| 1 | 0.029316 | CE155551 | C93060 | 週刊少年サンデー | 1970-08-09 | 第1群(合計話数:8-16話) |
| 2 | 0.224756 | CE155554 | C92231 | 週刊少年サンデー | 1970-08-09 | 第1群(合計話数:8-16話) |
| 3 | 0.801303 | CE155560 | C93019 | 週刊少年サンデー | 1970-08-09 | 第1群(合計話数:8-16話) |
| 4 | 0.200637 | CE155541 | C93060 | 週刊少年サンデー | 1970-08-16 | 第1群(合計話数:8-16話) |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_hist2, DIR_OUT, "hist2")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/hist2.csv'.
Show code cell source
# df_hist2の「掲載位置」に関するヒストグラムを「グループ」ごとに作成
# y軸のタイトルを「各話数」に設定
fig = px.histogram(
df_hist2, x="掲載位置", facet_col="グループ名", facet_col_wrap=1, height=600
).update_yaxes(title="各話数")
# ファセット(グループごとのヒストグラム)のタイトルを簡潔にする処理
# デフォルトではタイトルは「グループ=xxx」という形式になっている
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
# ヒストグラムを表示
show_fig(fig)
Show code cell content
# グループ名ごとの、ccnameとceidのユニーク数を集計
df_hist2.groupby(["グループ名"]).agg(
マンガ作品数=("ccid", "nunique"), マンガ各話数=("ceid", "nunique")
).reset_index()
| グループ名 | マンガ作品数 | マンガ各話数 | |
|---|---|---|---|
| 0 | 第1群(合計話数:8-16話) | 559 | 4472 |
| 1 | 第2群(合計話数:17-31話) | 566 | 4528 |
| 2 | 第3群(合計話数:32-81話) | 594 | 4752 |
| 3 | 第4群(合計話数:82-1956話) | 584 | 4672 |
Show code cell content
# データフレームを日付で降順にソート
df_ce_sorted = df_ce.sort_values("date", ascending=False)
# mcnameごとにグループ化し、最新のmiidを取得
# mcname2miid_latest = {mcname: 最新のmiid}
mcname2miid_latest = df_ce_sorted.groupby("mcname")["miid"].first().to_dict()
Show code cell content
# 例として、最新の週刊少年ジャンプに掲載されている作品を表示
df_ce[df_ce["miid"] == mcname2miid_latest["週刊少年ジャンプ"]][
["date", "ccname", "cename", "page_start_position"]
]
| date | ccname | cename | page_start_position | |
|---|---|---|---|---|
| 170928 | 2017-07-31 | ONE PIECE | 第872話 とろふわ | 0.006237 |
| 170929 | 2017-07-31 | 祝!ONE PIECE 20周年!!尾田さんとの思い出漫画! by しまぶー. | NaN | 0.126819 |
| 170930 | 2017-07-31 | ONE PIECE PARTY | CONGRATULATIONS ON 20 INCREDIBLE YEARS!! | 0.160083 |
| 170931 | 2017-07-31 | 僕のヒーローアカデミア | No.145 烈怒頼雄斗 2 | 0.180873 |
| 170932 | 2017-07-31 | 約束のネバーランド | 第47話 昔話 | 0.222453 |
| 170933 | 2017-07-31 | 食戟のソーマ | 223 フィールドを超えて | 0.268191 |
| 170934 | 2017-07-31 | Dr.STONE | Z=19 200万年の在処 | 0.309771 |
| 170935 | 2017-07-31 | 銀魂 | 第643訓 血と涙 | 0.351351 |
| 170936 | 2017-07-31 | ブラッククローバー | ページ 117 二人の空間魔法使い | 0.388773 |
| 170937 | 2017-07-31 | ROBOT×LASERBEAM | 17th round 強敵 | 0.426195 |
| 170938 | 2017-07-31 | 鬼滅の刃 | 第70話 人攫い | 0.467775 |
| 170939 | 2017-07-31 | クロスアカウント | #5 噂×嘘 | 0.509356 |
| 170940 | 2017-07-31 | 斉木楠雄のΨ難 | 第252χ 自慢の粘土Ψ工を披露しよう | 0.550936 |
| 170941 | 2017-07-31 | ハイキュー!! | 第262話 いつだって前のめり | 0.584200 |
| 170942 | 2017-07-31 | ゆらぎ荘の幽奈さん | 71 ラブラブバイト大作戦 | 0.679834 |
| 170943 | 2017-07-31 | シューダン! | 6 奮起する浜西 | 0.721414 |
| 170944 | 2017-07-31 | ぼくたちは勉強ができない | 問23. 天才たちの花園に[x]は不可欠である | 0.762994 |
| 170945 | 2017-07-31 | 火ノ丸相撲 | 第153番 未来 | 0.804574 |
| 170946 | 2017-07-31 | 青春兵器ナンバーワン | mission 37: ROMANCE DAWN | 0.846154 |
| 170947 | 2017-07-31 | HUNTER×HUNTER | No.364 思惑 | 0.879418 |
| 170948 | 2017-07-31 | 腹ペコのマリー | ペコ 20 恋するファッションショー | 0.920998 |
| 170949 | 2017-07-31 | 磯部磯兵衛物語~浮世はつらいよ~ | 第244話 拙者には娘さんが…で候 | 0.979210 |
密度プロット#
Show code cell content
# ヒストグラムと同様のデータを利用
df_dist = df_hist2.copy()
Show code cell content
# 可視化対象のDataFrameを確認
df_dist.head()
| 掲載位置 | ceid | ccid | mcname | date | グループ名 | |
|---|---|---|---|---|---|---|
| 0 | 0.373239 | CE155567 | C93019 | 週刊少年サンデー | 1970-08-02 | 第1群(合計話数:8-16話) |
| 1 | 0.029316 | CE155551 | C93060 | 週刊少年サンデー | 1970-08-09 | 第1群(合計話数:8-16話) |
| 2 | 0.224756 | CE155554 | C92231 | 週刊少年サンデー | 1970-08-09 | 第1群(合計話数:8-16話) |
| 3 | 0.801303 | CE155560 | C93019 | 週刊少年サンデー | 1970-08-09 | 第1群(合計話数:8-16話) |
| 4 | 0.200637 | CE155541 | C93060 | 週刊少年サンデー | 1970-08-16 | 第1群(合計話数:8-16話) |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_dist, DIR_OUT, "dist")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/dist.csv'.
Show code cell source
# df_distデータフレームを使用して密度プロットを作成
# "掲載位置"をx軸に、"グループ名"を色分けの基準にしてプロット
# 色はPortlandスタイルで指定
fig = create_distplot(
df_dist, x="掲載位置", color="グループ名", colors=px.colors.diverging.Portland
)
# グラフのレイアウトを更新
# ホバーモードを"x unified"に設定して、x軸に沿った統一されたホバー情報を表示
# 凡例をグラフの右上に配置(yanchorとxanchorで位置調整)
fig.update_layout(
hovermode="x unified", legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99)
)
# 作成したグラフを表示
show_fig(fig)
Show code cell content
# データフレームからユニークなマンガ雑誌名を取得
mcnames = df_dist["mcname"].unique()
# サブプロットを配置するための行数を計算
rows = len(mcnames)
# y軸の最大値を格納するためのリストを初期化
y_max_values = []
Show code cell source
# 複数のサブプロットを持つ図を作成。各マンガ雑誌名をサブプロットのタイトルとして設定
# vertical_spacingで縦方向のファセット間の余白を調整
fig = make_subplots(
rows=rows,
cols=1,
vertical_spacing=0.1,
subplot_titles=mcnames,
)
# マンガ雑誌名の数だけ繰り返し処理
for i, mcname in enumerate(mcnames):
# 現在のマンガ雑誌名に対応するデータをフィルタリング
df_mc = df_dist[df_dist["mcname"] == mcname].sort_values(
"グループ名", ignore_index=True
)
# 掲載位置の分布プロットを作成
distplot = create_distplot(
df_mc, x="掲載位置", color="グループ名", colors=px.colors.diverging.Portland
)
# 各サブプロットのy軸の最大値をリストに追加
y_max_values.append(np.max([trace.y for trace in distplot.data]))
# 作成した分布プロットを図に追加、可視化のために逆順でtraceを追加
for trace in distplot.data[::-1]:
# 凡例が重複しないよう、i==0のときのみ一つだけ表示
if i > 0:
trace.showlegend = False
fig.add_trace(trace, row=i + 1, col=1)
# 全サブプロットの中で最大のy軸値を計算
y_max = np.max(y_max_values)
# Y軸のラベルを表示し、表示範囲を最大値の1.1倍に調整
fig.update_yaxes(title_text="確率密度", range=[0, y_max * 1.1])
# X軸のラベルを下側のサブプロットのみに表示
fig.update_xaxes(title_text="掲載位置", row=rows, col=1)
# ホバーモードを"x unified"に設定して、x軸に沿った統一されたホバー情報を表示
# 各密度プロットが潰れてしまわないように、heightで高さを調整
fig.update_layout(hovermode="x unified", height=800)
# 作成した図を表示する
show_fig(fig)
箱ひげ図#
Show code cell content
# df_ceのデータをccname(マンガ作品名)ごとにグループ化し、各作品の掲載位置の開始点に関する統計情報を集計
# 'count' はその作品が何回掲載されたか、'mean' は掲載位置の平均値
df_tmp = (
df_ce.groupby("ccname")["page_start_position"].agg(["count", "mean"]).reset_index()
)
# 集計したデータを 'count'(掲載回数)に基づいて降順にソートし、
# トップ10の作品を抽出(最も多く掲載された作品10個を選ぶ)
df_tmp = df_tmp.sort_values("count", ascending=False).head(10)
# 抽出したトップ10の作品を 'mean'(平均掲載位置)に基づいて昇順にソート
# 平均掲載位置が低い(前の方に掲載される)作品が上位に来る
ccnames = df_tmp.sort_values("mean")["ccname"].to_list()
# 元のdf_ceデータフレームから、トップ10の作品名(ccname)に該当するデータのみを抽出
# reset_index(drop=True)は、新しいデータフレームのインデックスをリセットして整理するためのもの
df_box = df_ce[df_ce["ccname"].isin(ccnames)].reset_index(drop=True)
# 抽出したデータフレーム(df_box)のccname列をカテゴリー型に変換し、
# カテゴリーの順序を先ほどソートしたccnamesの順に設定
# これにより、データフレームの並び替えがこの順序に基づくようになる
df_box["ccname"] = pd.Categorical(df_box["ccname"], categories=ccnames, ordered=True)
# df_boxをccnameに基づいてソート(カテゴリー型なので、設定した順序に従ってソートされる)
# ignore_index=Trueは、ソート後のインデックスもリセットして整理するためのもの
df_box = df_box.sort_values("ccname", ignore_index=True)
Show code cell content
# 可視化用に保持するカラム
cols2rename = {
"ccname": "マンガ作品名",
"page_start_position": "掲載位置",
"ceid": "ceid",
"date": "date",
}
# 可視化用に列名を変更
df_box = format_cols(df_box, cols2rename)
Show code cell content
# 可視化対象のDataFrameを確認
df_box.head()
| マンガ作品名 | 掲載位置 | ceid | date | |
|---|---|---|---|---|
| 0 | ドカベン | 0.213768 | CE113217 | 1976-07-19 |
| 1 | ドカベン | 0.141304 | CE112518 | 1977-07-11 |
| 2 | ドカベン | 0.097473 | CE112532 | 1977-07-04 |
| 3 | ドカベン | 0.141304 | CE112548 | 1977-06-27 |
| 4 | ドカベン | 0.010830 | CE112561 | 1977-06-20 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_box, DIR_OUT, "box")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/box.csv'.
Show code cell source
# 'df_box' データフレームを使い、x軸にはマンガ作品名、y軸には各作品の掲載位置を設定
fig = px.box(df_box, x="マンガ作品名", y="掲載位置")
# 描画した箱ひげ図を表示する
show_fig(fig)
Show code cell content
# '名探偵コナン'というタイトルのマンガに関連するデータをdf_ceから抽出
# そのデータを 'page_start_position'(掲載位置の開始点)に基づいて降順にソート
# ソートされたデータから、特定のカラムのみを選択し、上位10行のみを表示
df_ce[df_ce["ccname"] == "名探偵コナン"].sort_values(
"page_start_position", ascending=False
)[["miname", "cename", "page_start_position", "pages"]].head(10)
| miname | cename | page_start_position | pages | |
|---|---|---|---|---|
| 152328 | 週刊少年サンデー 2011年 表示号数32 | FILE 783 菱形と菱形 | 0.801402 | 16.0 |
| 163865 | 週刊少年サンデー 2015年 表示号数29 | REVIVAL FILE 06 黎明(SSC55巻より) | 0.761411 | 16.0 |
| 152261 | 週刊少年サンデー 2011年 表示号数29 | FILE 780 魔法の料理 | 0.750000 | 16.0 |
| 152928 | 週刊少年サンデー 2012年 表示号数9 | FILE 805 ワタル・ブラザーズ | 0.748826 | 16.0 |
| 163695 | 週刊少年サンデー 2015年 表示号数22 | REVIVAL FILE 04 終極(SSC16巻より) | 0.721591 | 18.0 |
| 155282 | 週刊少年サンデー 2014年 表示号数8 | FILE 885 凧揚げ大会 | 0.720085 | 16.0 |
| 154799 | 週刊少年サンデー 2013年 表示号数39 | FILE 870 願いが叶った時に… | 0.720085 | 16.0 |
| 163667 | 週刊少年サンデー 2015年 表示号数21 | REVIVAL FILE 03 気配(SSC16巻より) | 0.717308 | 16.0 |
| 154928 | 週刊少年サンデー 2013年 表示号数44 | FILE 874 赤き昔日 | 0.714592 | 16.0 |
| 152677 | 週刊少年サンデー 2011年 表示号数49 | FILE 795 炎へと回帰する運 | 0.713992 | 16.0 |
Show code cell content
# df_ceから 'cename'(各話名)に 'REVIVAL FILE' が含まれる行を抽出
# 抽出されたデータから特定のカラムのみを選択
df_ce[df_ce["cename"].str.contains("REVIVAL FILE") > 0][
["miname", "ccname", "cename", "page_start_position"]
]
| miname | ccname | cename | page_start_position | |
|---|---|---|---|---|
| 163600 | 週刊少年サンデー 2015年 表示号数19 | 名探偵コナン | REVIVAL FILE 01 邂逅(SSC16巻より) | 0.449187 |
| 163627 | 週刊少年サンデー 2015年 表示号数20 | 名探偵コナン | REVIVAL FILE 02 消滅(SSC16巻より) | 0.439271 |
| 163667 | 週刊少年サンデー 2015年 表示号数21 | 名探偵コナン | REVIVAL FILE 03 気配(SSC16巻より) | 0.717308 |
| 163695 | 週刊少年サンデー 2015年 表示号数22 | 名探偵コナン | REVIVAL FILE 04 終極(SSC16巻より) | 0.721591 |
| 163828 | 週刊少年サンデー 2015年 表示号数28 | 名探偵コナン | REVIVAL FILE 05 月下(SSC55巻より) | 0.469262 |
| 163865 | 週刊少年サンデー 2015年 表示号数29 | 名探偵コナン | REVIVAL FILE 06 黎明(SSC55巻より) | 0.761411 |
| 163890 | 週刊少年サンデー 2015年 表示号数30 | 名探偵コナン | REVIVAL FILE 07 白昼(SSC55巻より) | 0.697154 |
| 163919 | 週刊少年サンデー 2015年 表示号数31 | 名探偵コナン | REVIVAL FILE 08 落日(SSC55巻より) | 0.656379 |
バイオリンプロット#
Show code cell content
# 箱ひげ図と同じデータを利用
df_violin = df_box.copy()
Show code cell content
# 可視化対象のDataFrameを確認
df_violin.head()
| マンガ作品名 | 掲載位置 | ceid | date | |
|---|---|---|---|---|
| 0 | ドカベン | 0.213768 | CE113217 | 1976-07-19 |
| 1 | ドカベン | 0.141304 | CE112518 | 1977-07-11 |
| 2 | ドカベン | 0.097473 | CE112532 | 1977-07-04 |
| 3 | ドカベン | 0.141304 | CE112548 | 1977-06-27 |
| 4 | ドカベン | 0.010830 | CE112561 | 1977-06-20 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_violin, DIR_OUT, "violin")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/violin.csv'.
Show code cell source
# df_violinデータフレームを使用してバイオリンプロットを描画
# "マンガ作品名"をx軸に、"掲載位置"をy軸に設定し、図の高さを500に設定
fig = px.violin(df_violin, x="マンガ作品名", y="掲載位置", height=500)
# バイオリンプロットの幅を1に設定し、平均線を表示
fig.update_traces(width=1, meanline_visible=True)
# バイオリンプロットを重ねて表示し、バイオリン間の間隔を0に設定
fig.update_layout(violinmode="overlay", violingap=0)
# 作成したバイオリンプロットを表示
show_fig(fig)
Show code cell content
# 新たな可視化のためにdf_violinのコピーを作成
df_violin2 = df_violin.copy()
# ユニークな 'ceid' の数を計算し、新しいカラム 'half_count' に保存
df_violin2["half_count"] = df_violin2.groupby("マンガ作品名")["ceid"].transform(
lambda x: x.nunique() / 2
)
# データをマンガ作品名とdateでソートしておく
df_violin2 = df_violin2.sort_values(["マンガ作品名", "date"], ignore_index=True)
# cumcountメソッドを用いて各マンガ作品名ごとに話数インデックス(ceno)を振る
df_violin2["ceno"] = df_violin2.groupby("マンガ作品名").cumcount()
# 掲載時期の前半・後半を割り当てる
# まずはデフォルト値として「前半」を割り当てておく
df_violin2["連載前半"] = True
# 話数インデックスが合計話数の半分以上の場合は、掲載時期を「後半」に更新
df_violin2.loc[df_violin2["ceno"] >= df_violin2["half_count"], "連載前半"] = False
Show code cell content
# 可視化対象のDataFrameを確認
df_violin2.head()
| マンガ作品名 | 掲載位置 | ceid | date | half_count | ceno | 連載前半 | |
|---|---|---|---|---|---|---|---|
| 0 | ドカベン | 0.008451 | CE116042 | 1972-04-24 | 317.5 | 0 | True |
| 1 | ドカベン | 0.188406 | CE116029 | 1972-05-01 | 317.5 | 1 | True |
| 2 | ドカベン | 0.119565 | CE116013 | 1972-05-08 | 317.5 | 2 | True |
| 3 | ドカベン | 0.113333 | CE115997 | 1972-05-15 | 317.5 | 3 | True |
| 4 | ドカベン | 0.094156 | CE115981 | 1972-05-22 | 317.5 | 4 | True |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_violin2, DIR_OUT, "violin2")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/violin2.csv'.
Show code cell source
# df_violinデータフレームを使用してスプリットバイオリンプロットを作成
# "マンガ作品名"をx軸に、"掲載位置"をy軸に設定し、"連載前半"をスプリット基準にしてプロット
# バイオリンの幅を1に設定
fig = create_split_violin_plot(
df_violin2, x="マンガ作品名", y="掲載位置", split="連載前半", width=1
)
# グラフのレイアウトを更新
# 凡例をグラフの左上に配置(yanchorとxanchorで位置調整)し、グラフの高さを500に設定
fig.update_layout(
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), height=500
)
# 作成したバイオリンプロットを表示
show_fig(fig)
リッジラインプロット#
Show code cell content
# ヒストグラムで用いたものと同じデータを使用
df_ridge = df_hist2.copy()
Show code cell content
# データをccnameとdateでソートしておく
df_ridge = df_ridge.sort_values(["ccid", "date"], ignore_index=True)
# cumcountメソッドを用いて各マンガ作品名ごとに話数インデックス(ceno)を振る
df_ridge["話数"] = df_ridge.groupby("ccid").cumcount() + 1
Show code cell content
# 可視化対象のDataFrameを確認
df_ridge.head()
| 掲載位置 | ceid | ccid | mcname | date | グループ名 | 話数 | |
|---|---|---|---|---|---|---|---|
| 0 | 0.015291 | CE71082 | C109295 | 週刊少年ジャンプ | 1980-08-18 | 第1群(合計話数:8-16話) | 1 |
| 1 | 0.198777 | CE71068 | C109295 | 週刊少年ジャンプ | 1980-08-25 | 第1群(合計話数:8-16話) | 2 |
| 2 | 0.266055 | CE71051 | C109295 | 週刊少年ジャンプ | 1980-09-01 | 第1群(合計話数:8-16話) | 3 |
| 3 | 0.394495 | CE71038 | C109295 | 週刊少年ジャンプ | 1980-09-08 | 第1群(合計話数:8-16話) | 4 |
| 4 | 0.266055 | CE71019 | C109295 | 週刊少年ジャンプ | 1980-09-15 | 第1群(合計話数:8-16話) | 5 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_ridge, DIR_OUT, "ridge")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/ridge.csv'.
Show code cell source
# df_ridgeデータフレームを使ってリッジラインプロットを作成
# "話数"をy軸に、"掲載位置"をx軸に設定し、図の高さを500に設定
# orientation="h"で水平方向のバイオリンプロットを作成
fig = px.violin(
df_ridge, y="話数", x="掲載位置", orientation="h", points=False, height=500
)
# side="positive"でバイオリンの片側だけを表示し、width=3でバイオリンの幅を設定
fig.update_traces(side="positive", width=3)
# y軸の表示範囲を設定
# 0からdf_ridge内の"話数"の最大値+1までの範囲に設定
fig.update_yaxes(range=[0, df_ridge["話数"].max() + 1])
# 作成したリッジラインプロットを表示
show_fig(fig)
Show code cell source
# ファセットがグループ名順に並ぶように事前にソートしておく
df_ridge = df_ridge.sort_values(["グループ名"], ignore_index=True)
# df_ridgeデータフレームを使ってリッジラインプロットを作成
# "話数"をy軸に、"掲載位置"をx軸に設定し、図の高さを400に設定
# facet_colでグループ名を指定することでグループごとの可視化を実現
# orientation="h"で水平方向のバイオリンプロットを作成
fig = px.violin(
df_ridge,
y="話数",
x="掲載位置",
orientation="h",
facet_col="グループ名",
points=False,
height=400,
)
# side="positive"でバイオリンの片側だけを表示し、width=3でバイオリンの幅を設定
fig.update_traces(side="positive", width=3)
# y軸の表示範囲を設定
# 0からdf_ridge内の"話数"の最大値+1までの範囲に設定
fig.update_yaxes(range=[0, df_ridge["話数"].max() + 1])
# ファセット(グループごとのリッジラインプロット)のタイトルを簡潔にする処理
# デフォルトではタイトルは「グループ=xxx」という形式になっている
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
# 作成したリッジラインプロットを表示
show_fig(fig)
Show code cell content
# データフレームからユニークな話数を取得
cenos = sorted(df_ridge["話数"].unique())
# サブプロットを配置するための行数を計算
rows = len(cenos)
# y軸の最大値を格納するためのリストを初期化
y_max_values = []
Show code cell source
# 複数のサブプロットを持つ図を作成。各話数をサブプロットのタイトルとして設定
# vertical_spacingで縦方向のファセット間の余白を調整
fig = make_subplots(
rows=rows,
cols=1,
vertical_spacing=0.01,
)
# 話数の数だけ繰り返し処理
for i, ceno in enumerate(cenos):
# 現在の話数に対応するデータをフィルタリング
df_ceno = df_ridge[df_ridge["話数"] == ceno].sort_values(
"グループ名", ignore_index=True
)
# 掲載位置の分布プロットを作成
distplot = create_distplot(
df_ceno, x="掲載位置", color="グループ名", colors=px.colors.diverging.Portland
)
# 作成した分布プロットを図に追加、可視化のために逆順でtraceを追加
for trace in distplot.data[::-1]:
# 凡例が重複しないよう、i==0のときのみ一つだけ表示
if i > 0:
trace.showlegend = False
fig.add_trace(trace, row=i + 1, col=1)
# Y軸のラベルとして話数を表示
fig.update_yaxes(title_text=f"{ceno}話目", row=i + 1)
# X軸のメモリを表示しないように設定
fig.update_xaxes(showticklabels=False, row=i + 1)
# X軸のラベルを下側のサブプロットのみに表示
fig.update_xaxes(title_text="掲載位置", showticklabels=True, row=rows)
# ホバーモードを"x unified"に設定して、x軸に沿った統一されたホバー情報を表示
# 各密度プロットが潰れてしまわないように、heightで高さを調整
fig.update_layout(hovermode="x unified", height=800)
# 作成した図を表示する
show_fig(fig)
Show code cell content
# 週刊少年ジャンプのマンガ作品のみを抽出
df_ridge_jump = df_ridge[df_ridge["mcname"] == "週刊少年ジャンプ"].reset_index(
drop=True
)
Show code cell content
# 可視化対象のDataFrameを確認
df_ridge_jump.head()
| 掲載位置 | ceid | ccid | mcname | date | グループ名 | 話数 | |
|---|---|---|---|---|---|---|---|
| 0 | 0.015291 | CE71082 | C109295 | 週刊少年ジャンプ | 1980-08-18 | 第1群(合計話数:8-16話) | 1 |
| 1 | 0.629393 | CE74085 | C89161 | 週刊少年ジャンプ | 1976-08-16 | 第1群(合計話数:8-16話) | 8 |
| 2 | 0.808307 | CE74103 | C89161 | 週刊少年ジャンプ | 1976-08-09 | 第1群(合計話数:8-16話) | 7 |
| 3 | 0.565495 | CE74115 | C89161 | 週刊少年ジャンプ | 1976-08-02 | 第1群(合計話数:8-16話) | 6 |
| 4 | 0.719870 | CE74134 | C89161 | 週刊少年ジャンプ | 1976-07-26 | 第1群(合計話数:8-16話) | 5 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_ridge_jump, DIR_OUT, "ridge_jump")
DataFrame is saved as '../../../data/cm/output/vol1/04/dists/ridge_jump.csv'.
Show code cell source
# 複数のサブプロットを持つ図を作成。各話数をサブプロットのタイトルとして設定
# vertical_spacingで縦方向のファセット間の余白を調整
fig = make_subplots(
rows=rows,
cols=1,
vertical_spacing=0.01,
)
# 話数の数だけ繰り返し処理
for i, ceno in enumerate(cenos):
# 現在の話数に対応するデータをフィルタリング
df_ceno = df_ridge_jump[df_ridge_jump["話数"] == ceno].sort_values(
"グループ名", ignore_index=True
)
# 掲載位置の分布プロットを作成
distplot = create_distplot(
df_ceno, x="掲載位置", color="グループ名", colors=px.colors.diverging.Portland
)
# 作成した分布プロットを図に追加、可視化のために逆順でtraceを追加
for trace in distplot.data[::-1]:
# 凡例が重複しないよう、i==0のときのみ一つだけ表示
if i > 0:
trace.showlegend = False
fig.add_trace(trace, row=i + 1, col=1)
# Y軸のラベルとして話数を表示
fig.update_yaxes(title_text=f"{ceno}話目", row=i + 1)
# X軸のメモリを表示しないように設定
fig.update_xaxes(showticklabels=False, row=i + 1)
# X軸のラベルを下側のサブプロットのみに表示
fig.update_xaxes(title_text="掲載位置", showticklabels=True, row=rows)
# ホバーモードを"x unified"に設定して、x軸に沿った統一されたホバー情報を表示
# 各密度プロットが潰れてしまわないように、heightで高さを調整
fig.update_layout(hovermode="x unified", height=800)
# 作成した図を表示する
show_fig(fig)