マンガデータの量を見る#
準備#
Import#
Show code cell content
# warningsモジュールのインポート
import warnings
# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Show 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
変数#
Show code cell content
# マンガデータが保存されているディレクトリのパス
DIR_IN = Path("../../../data/cm/input")
# 分析結果の出力先ディレクトリのパス
DIR_OUT = (
DIR_IN.parent / "output" / Path.cwd().parts[-2] / Path.cwd().parts[-1] / "amounts"
)
Show code cell content
# 読み込み対象ファイル
# Comic Episode関連のファイル名
FN_CE = "cm_ce.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 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
Show 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
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)
Show code cell content
# date列をdatetime型に変換
df_ce["date"] = pd.to_datetime(df_ce["date"])
棒グラフ#
Show code cell content
# マンガ作品ごとの掲載話数を集計するためのDataFrameを作成
# 'groupby' と 'nunique' を使用して、各マンガ作品ごとにユニークな 'ceid'(掲載話数)を数える
df_bar = df_ce.groupby(["ccname"])["ceid"].nunique().reset_index(name="n_ce")
# 掲載話数が多い順にデータを並び替えて、上位N_CC件のデータを選択
# 'sort_values' でソートし、'head(20)' で上位20件を取得
df_bar = df_bar.sort_values(by="n_ce", ascending=False, ignore_index=True).head(20)
# 可視化のために'rename'メソッドを用いて列名をわかりやすい名前に変更
df_bar = df_bar.rename(columns={"ccname": "マンガ作品名", "n_ce": "合計話数"})
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_bar.head()
| マンガ作品名 | 合計話数 | |
|---|---|---|
| 0 | こちら葛飾区亀有公園前派出所 | 1968 |
| 1 | はじめの一歩 | 1186 |
| 2 | 名探偵コナン | 1009 |
| 3 | ONE PIECE | 893 |
| 4 | MAJOR | 748 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar, DIR_OUT, "bar")
DataFrame is saved as '../../../data/cm/output/vol1/04/amounts/bar.csv'.
Show code cell source
# 'px.bar' を使用して棒グラフを作成
# x軸には '合計話数'、y軸には 'マンガ作品名' を設定
# 'orientation' を 'h' に設定することで、棒グラフを水平(横向き)に表示
# 'height' でグラフの高さを指定
fig = px.bar(df_bar, x="合計話数", y="マンガ作品名", orientation="h", height=500)
# 作成した棒グラフを表示
show_fig(fig)
集合棒グラフ#
Show code cell content
# 10年区切りの年代情報を追加
df_ce = add_years_to_df(df_ce)
Show code cell content
# 「ccname」と「years」列で集計し、ユニークな「ceid」の数を「n_ce」列に格納
df_gbar = df_ce.groupby(["ccname", "years"])["ceid"].nunique().reset_index(name="n_ce")
# df_barに含まれるから「マンガ作品名」の上位10作品をリストに格納
ccnames = list(df_bar["マンガ作品名"])[:10]
# df_gbarをフィルタリングして、ccnamesに含まれるccnameのみを選択
df_gbar = df_gbar[df_gbar["ccname"].isin(ccnames)].reset_index(drop=True)
# 「ccname」列と「years」列を使ってdf_gbarをリサンプリング
df_gbar = resample_df_by_col_and_years(df_gbar, "ccname")
# 「ccname」列をカテゴリカルデータとして扱い、ccnamesの順序で並べ替え
df_gbar["ccname"] = pd.Categorical(df_gbar["ccname"], categories=ccnames, ordered=True)
# 「ccname」と「years」でソートし、インデックスをリセット
df_gbar = df_gbar.sort_values(["ccname", "years"], ignore_index=True)
# 可視化用に列名をわかりやすい日本語の名前に変更
df_gbar = df_gbar.rename(
columns={"ccname": "マンガ作品名", "years": "年代", "n_ce": "合計話数"}
)
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_gbar.head()
| マンガ作品名 | 年代 | 合計話数 | |
|---|---|---|---|
| 0 | こちら葛飾区亀有公園前派出所 | 1970 | 160 |
| 1 | こちら葛飾区亀有公園前派出所 | 1980 | 501 |
| 2 | こちら葛飾区亀有公園前派出所 | 1990 | 482 |
| 3 | こちら葛飾区亀有公園前派出所 | 2000 | 492 |
| 4 | こちら葛飾区亀有公園前派出所 | 2010 | 333 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_gbar, DIR_OUT, "gbar")
DataFrame is saved as '../../../data/cm/output/vol1/04/amounts/gbar.csv'.
Show code cell source
# df_gbarデータを使用して棒グラフを作成
# Y軸にマンガ作品名をとり、X軸に合計話数をとり、色は年代別に表示
# orientationで水平方向の横棒グラフを指定し、barmode=groupで集合棒グラフ化
# グラフの高さは600に調整し、カラーパレットはPortlandを指定
fig = px.bar(
df_gbar,
x="合計話数",
y="マンガ作品名",
color="年代",
orientation="h",
barmode="group",
height=600,
color_discrete_sequence=px.colors.diverging.Portland,
)
# 凡例の位置を図の右上に固定
# yanchorとxanchorは凡例の基準点(top:上部、right:右端)を指定
# yとxはその基準点の位置を指定
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99))
# 集合棒グラフを表示
show_fig(fig)
積上げ棒グラフ#
Show code cell content
# 比較のため、集合棒グラフと同じDataFrameを利用
df_sbar = df_gbar.copy()
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_sbar.head()
| マンガ作品名 | 年代 | 合計話数 | |
|---|---|---|---|
| 0 | こちら葛飾区亀有公園前派出所 | 1970 | 160 |
| 1 | こちら葛飾区亀有公園前派出所 | 1980 | 501 |
| 2 | こちら葛飾区亀有公園前派出所 | 1990 | 482 |
| 3 | こちら葛飾区亀有公園前派出所 | 2000 | 492 |
| 4 | こちら葛飾区亀有公園前派出所 | 2010 | 333 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_sbar, DIR_OUT, "sbar")
DataFrame is saved as '../../../data/cm/output/vol1/04/amounts/sbar.csv'.
Show code cell source
# df_gbarデータを使用して棒グラフを作成
# Y軸にマンガ作品名をとり、X軸に合計話数をとり、色は年代別に表示
# orientationで水平方向の横棒グラフを指定し、barmode=stackで積上げ棒グラフ化
# グラフの高さは400に調整し、カラーパレットはPortlandを指定
fig = px.bar(
df_gbar,
x="合計話数",
y="マンガ作品名",
color="年代",
orientation="h",
barmode="stack",
height=400,
color_discrete_sequence=px.colors.diverging.Portland,
)
# 凡例の位置を図の右上に固定
# yanchorとxanchorは凡例の基準点(top:上部、right:右端)を指定
# yとxはその基準点の位置を指定
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99))
# 積上げ棒グラフを表示
show_fig(fig)
ヒートマップ#
Show code cell content
# 1年区切りの年代情報を追加
df_ce = add_years_to_df(df_ce, unit_years=1)
# df_ceをdate順でソートし、全ccnameを事前に抽出しておく、時間順に並んだヒートマップを作成するための工夫
ccnames = (
df_ce.sort_values(["date", "ccid"], ignore_index=True)["ccname"].unique().tolist()
)
Show code cell content
# 「ccname」と「years」と「mcname」列で集計し、ユニークな「ceid」の数を「n_ce」列に格納
df_hm = (
df_ce.groupby(["mcname", "ccname", "years"])["ceid"]
.nunique()
.reset_index(name="n_ce")
)
# ccnamesのうち、df_barの「マンガ作品名」の上位10作品を抽出
# 直接list(df_bar["マンガ作品名"])[:20]を用いないのはccnamesの順序を維持するため
ccnames = [ccname for ccname in ccnames if ccname in list(df_bar["マンガ作品名"])[:20]]
# df_hmをフィルタリングして、ccnamesに含まれるccnameのみを選択
df_hm = df_hm[df_hm["ccname"].isin(ccnames)].reset_index(drop=True)
# 「ccname」列と「years」列を使ってdf_gbarをリサンプリング
df_hm = resample_df_by_col_and_years(df_hm, "ccname")
# 「ccname」列をカテゴリカルデータとして扱い、ccnamesの順序で並べ替え
df_hm["ccname"] = pd.Categorical(df_hm["ccname"], categories=ccnames, ordered=True)
# 「ccname」と「years」でソートし、インデックスをリセット
df_hm = df_hm.sort_values(["ccname", "years"], ignore_index=True)
# 可視化用に列名をわかりやすい日本語の名前に変更
df_hm = df_hm.rename(
columns={
"mcname": "マンガ雑誌名",
"ccname": "マンガ作品名",
"years": "掲載年",
"n_ce": "合計話数",
}
)
Show code cell content
# 可視化対象のDataFrameの内容を確認
df_hm.head()
| マンガ雑誌名 | マンガ作品名 | 掲載年 | 合計話数 | |
|---|---|---|---|---|
| 0 | 週刊少年サンデー | ダメおやじ | 1970 | 9 |
| 1 | 週刊少年サンデー | ダメおやじ | 1971 | 51 |
| 2 | 週刊少年サンデー | ダメおやじ | 1972 | 51 |
| 3 | 週刊少年サンデー | ダメおやじ | 1973 | 50 |
| 4 | 週刊少年サンデー | ダメおやじ | 1974 | 50 |
Show code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_hm, DIR_OUT, "hm")
DataFrame is saved as '../../../data/cm/output/vol1/04/amounts/hm.csv'.
Show code cell source
# df_hmデータを使ってヒートマップを作成
# x軸に掲載年、y軸にマンガ作品名、z軸に合計話数を設定
fig = px.density_heatmap(df_hm, x="掲載年", y="マンガ作品名", z="合計話数", height=600)
# ヒートマップを表示
show_fig(fig)
Show code cell content
# 週刊少年ジャンプの2009年のデータを選択し、雑誌巻号ごとにCEIDのユニーク数をカウント
# グループ化して、新しい列n_ceとして格納
df_mi = (
df_ce[(df_ce["mcname"] == "週刊少年ジャンプ") & (df_ce["years"] == "2009")]
.groupby(["miid", "miname"])["ceid"]
.nunique()
.reset_index(name="n_ce")
)
# n_ceで降順ソートして上位5件を表示
df_mi.sort_values("n_ce", ascending=False).head()
| miid | miname | n_ce | |
|---|---|---|---|
| 47 | M542881 | 週刊少年ジャンプ 2009年 表示号数6 | 44 |
| 0 | M542834 | 週刊少年ジャンプ 2010年 表示号数03 | 43 |
| 18 | M542852 | 週刊少年ジャンプ 2009年 表示号数37 | 37 |
| 43 | M542877 | 週刊少年ジャンプ 2009年 表示号数11 | 26 |
| 17 | M542851 | 週刊少年ジャンプ 2009年 表示号数39 | 24 |
Show code cell content
# miidが'M542881'であるレコードを選択し、必要な列のみを抽出
# ccname, cename, page_start, pages, dateを選択
# page_startでソートして、ページの開始順に並べ替え
df_ce[df_ce["miid"] == "M542881"][
["ccname", "cename", "page_start", "pages", "date"]
].sort_values("page_start")
| ccname | cename | page_start | pages | date | |
|---|---|---|---|---|---|
| 39979 | NARUTO-ナルト- | 430:ナルト帰還!! | 9.0 | 33.0 | 2009-01-28 |
| 39980 | ONE PIECE | 第527話 紅蓮地獄 | 43.0 | 19.0 | 2009-01-28 |
| 39981 | トリコ | グルメ 32 ロックドラム!! | 63.0 | 19.0 | 2009-01-28 |
| 39982 | BLEACH | 340. The Antagonizer | 83.0 | 19.0 | 2009-01-28 |
| 39983 | 家庭教師ヒットマンREBORN! | 標的 224 XANXUS VS. Rasiel | 103.0 | 17.0 | 2009-01-28 |
| 39984 | ONE PIECE | 消されたライセンス | 130.0 | 1.0 | 2009-01-28 |
| 39985 | こちら葛飾区亀有公園前派出所 | コタツとみかん | 130.0 | 1.0 | 2009-01-28 |
| 39986 | ぬらりひょんの孫 | 1ゆら 2カナ 3つらら | 131.0 | 1.0 | 2009-01-28 |
| 39988 | BLEACH | 白哉玉 | 132.0 | 1.0 | 2009-01-28 |
| 39987 | 魔人探偵脳噛ネウロ | 弥子の正月 | 132.0 | 1.0 | 2009-01-28 |
| 39989 | アスクレピオス | はねつきっス!! | 133.0 | 1.0 | 2009-01-28 |
| 39990 | To LOVEる -とらぶる- | ダーク・オ・セチー | 134.0 | 1.0 | 2009-01-28 |
| 39991 | アイシールド21 | 一富士二鷹三茄子は最高に縁起のいい初夢です | 134.0 | 1.0 | 2009-01-28 |
| 39992 | マイスター | 蹴り初め | 135.0 | 1.0 | 2009-01-28 |
| 39993 | 銀魂 | 汁粉と善哉 | 136.0 | 1.0 | 2009-01-28 |
| 39994 | SKET DANCE | 今年の目標 | 136.0 | 1.0 | 2009-01-28 |
| 39995 | 黒子のバスケ | 本の虫 | 137.0 | 1.0 | 2009-01-28 |
| 39997 | PSYREN-サイレン- | おねがいごと | 138.0 | 1.0 | 2009-01-28 |
| 39996 | 家庭教師ヒットマンREBORN! | ランボはどこ? | 138.0 | 1.0 | 2009-01-28 |
| 39998 | ぼっけさん | ヒノとサユの初詣 | 139.0 | 1.0 | 2009-01-28 |
| 40000 | トリコ | トリコの正月 | 140.0 | 1.0 | 2009-01-28 |
| 39999 | バクマン。 | 俺達に正月休みはない! | 140.0 | 1.0 | 2009-01-28 |
| 40001 | いぬまるだしっ | お正月の思い出作文 | 141.0 | 1.0 | 2009-01-28 |
| 40002 | ピューと吹く!ジャガー | ぼやき初め | 142.0 | 1.0 | 2009-01-28 |
| 40003 | NARUTO-ナルト- | 10年目の真実 | 142.0 | 1.0 | 2009-01-28 |
| 40004 | SKET DANCE | 第72話 ファッショナブル侍 | 145.0 | 17.0 | 2009-01-28 |
| 40005 | ダブルマメダイチ | NaN | 163.0 | 49.0 | 2009-01-28 |
| 40006 | バクマン。 | 20ページ 未来と階段 | 213.0 | 21.0 | 2009-01-28 |
| 40007 | 銀魂 | 第243訓 何回見てもラピュタはいい | 237.0 | 19.0 | 2009-01-28 |
| 40008 | アイシールド21 | 312th down 新世代へ | 257.0 | 19.0 | 2009-01-28 |
| 40009 | いぬまるだしっ | 第20回 1 「たまこ先生の年賀状」 | 299.0 | 3.0 | 2009-01-28 |
| 40010 | いぬまるだしっ | 第20回 2 「アレに似てるおじさん」 | 302.0 | 4.0 | 2009-01-28 |
| 40011 | いぬまるだしっ | 第20回 3 「善と悪」 | 306.0 | 2.0 | 2009-01-28 |
| 40012 | 黒子のバスケ | 第4Q まともじゃないかもしんないスね | 311.0 | 19.0 | 2009-01-28 |
| 40013 | ぼっけさん | 第3怪 存在の証明 | 331.0 | 27.0 | 2009-01-28 |
| 40014 | マイスター | Play.5 スタープレーヤー | 359.0 | 19.0 | 2009-01-28 |
| 40015 | こちら葛飾区亀有公園前派出所 | 初夢の正月クルーズの巻 | 379.0 | 19.0 | 2009-01-28 |
| 40016 | PSYREN-サイレン- | CALL.53 “痛み” | 399.0 | 19.0 | 2009-01-28 |
| 40017 | ぬらりひょんの孫 | 第41幕 百鬼夜行対八十八鬼夜行 | 419.0 | 19.0 | 2009-01-28 |
| 40018 | 魔人探偵脳噛ネウロ | 第188話 距【きょり】 | 439.0 | 19.0 | 2009-01-28 |
| 40019 | To LOVEる -とらぶる- | トラブル 131 クイーンの反抗 | 459.0 | 19.0 | 2009-01-28 |
| 40020 | アスクレピオス | 第15話 結紮!! | 481.0 | 19.0 | 2009-01-28 |
| 40021 | ピューと吹く!ジャガー | 映画公開記念特別編 ・~いま、吹きにゆきます~~の撮影にいま、ゆきます~ | 511.0 | 1.0 | 2009-01-28 |
| 40022 | ピューと吹く!ジャガー | 週刊!?ハミ通SP 特集:文字で見る映画の裏側 | 512.0 | 2.0 | 2009-01-28 |