下巻 第1章 解答例#

ここでは、 本書の学習内容の定着 を目的とした練習問題とその解答・解説を掲載します。 なお、問題の性質上、本書で取り上げた処理と重複することがあります。 ご了承ください。

前提#

以下のように、ライブラリのインポートと変数の定義が完了していることを前提とします。

Hide 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.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")
Hide code cell content
# 読み込み対象ファイル名の定義

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

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

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

# PacKaGeとPlatForm関連のファイル名
FN_PKG_PF = "gm_pkg_pf.csv"
Hide code cell content
# 国内主要ゲームメーカーのプラットフォームとメーカー名の対応辞書
# キー: プラットフォーム名、値: メーカー名の略称
PF2MK = {
    "プレイステーション": "ソニー",
    "プレイステーション2": "ソニー",
    "プレイステーション・ポータブル": "ソニー",
    "プレイステーション3": "ソニー",
    "プレイステーションVita": "ソニー",
    "プレイステーション4": "ソニー",
    "ゲームアーカイブス": "ソニー",
    "SG-1000": "セガ",
    "SC-3000": "セガ",
    "SEGAマーク3": "セガ",
    "セガ・マスターシステム": "セガ",
    "メガドライブ": "セガ",
    "ゲームギア": "セガ",
    "セガサターン": "セガ",
    "ドリームキャスト": "セガ",
    "ファミリーコンピュータ": "任天堂",
    "ゲームボーイ": "任天堂",
    "スーパーファミコン": "任天堂",
    "NINTENDO64": "任天堂",
    "ゲームボーイアドバンス": "任天堂",
    "ニンテンドーゲームキューブ": "任天堂",
    "ニンテンドーDS": "任天堂",
    "ニンテンドー3DS": "任天堂",
    "Wii": "任天堂",
    "WiiU": "任天堂",
    "NintendoSwitch": "任天堂",
}
Hide code cell content
# pandasのweekday関数で取得できる曜日の数値と実際の曜日名を対応させる辞書を定義
# 0:月曜日, 1:火曜日, ... , 6:日曜日
WEEKDAY2YOBI = {
    0: "月",
    1: "火",
    2: "水",
    3: "木",
    4: "金",
    5: "土",
    6: "日",
}
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
# マンガデータの読み込み
df_ce = pd.read_csv(DIR_CM / FN_CE)
df_cc_crt = pd.read_csv(DIR_CM / FN_CC_CRT)
# アニメデータの読み込み
df_ae = pd.read_csv(DIR_AN / FN_AE)
# ゲームデータの読み込み
df_pkg_pf = pd.read_csv(DIR_GM / FN_PKG_PF)

基礎 問題1:アニメ作品別の合計話数#

関連セクション: 棒グラフ

本書では長寿アニメ作品の合計話数を確認しました。 ここでは、アニメ作品ごとの合計話数を横棒グラフで可視化してみましょう。

  • df_aeを用いて、アニメ作品(acname)ごとの合計話数を集計してください

  • 合計話数の 上位10作品 を対象に横棒グラフを作成してください

Hide code cell source
# アニメ作品ごとの合計話数を集計
df_an = df_ae.groupby("acname")["aeid"].nunique().reset_index(name="n_ae")

# 合計話数の上位10作品を抽出
df_an = df_an.sort_values("n_ae", ascending=False, ignore_index=True).head(10)

# 可視化用にカラム名を変更
df_an = df_an.rename(columns={"acname": "アニメ作品名", "n_ae": "合計話数"})

# 横棒グラフで可視化
fig = px.bar(df_an, y="アニメ作品名", x="合計話数", orientation="h", height=400)

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

解説

groupby()nunique()を組み合わせることで、各アニメ作品のユニークな話数を集計できます。

横棒グラフは、カテゴリ名が長い場合に特に有効です。 アニメ作品名のような日本語の長い名称も、横棒グラフなら見やすく表示できます。

関連セクション: 詳しくは棒グラフを参照してください。

基礎 問題2:プラットフォーム別のパッケージ数#

関連セクション: 棒グラフ

ゲーム市場では、プラットフォームごとにリリースされるゲームパッケージ数に大きな差があります。 どのプラットフォームが最も多くのゲームをリリースしているか確認してみましょう。

  • df_pkg_pfを用いて、プラットフォーム(pfname)ごとの合計パッケージ数を集計してください

  • 合計パッケージ数の 上位10 プラットフォームを対象に横棒グラフを作成してください

Hide code cell source
# プラットフォームごとの合計パッケージ数を集計
df_gm = df_pkg_pf.groupby("pfname")["pkgid"].nunique().reset_index(name="n_pkg")

# 合計パッケージ数の上位10プラットフォームを抽出
df_gm = df_gm.sort_values("n_pkg", ascending=False, ignore_index=True).head(10)

# 可視化用にカラム名を変更
df_gm = df_gm.rename(columns={"pfname": "プラットフォーム名", "n_pkg": "パッケージ数"})

# 横棒グラフで可視化
fig = px.bar(
    df_gm, y="プラットフォーム名", x="パッケージ数", orientation="h", height=400
)

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

解説

プラットフォームごとのパッケージ数を集計し、横棒グラフで可視化しました。

結果を見ると、プレイステーションシリーズが上位を占めていることがわかります。 ソニー[1]のプラットフォームが市場で大きなシェアを持っていたことが読み取れます。

関連セクション: 詳しくは棒グラフを参照してください。

標準 問題3:ソニー製プラットフォームのパッケージ数#

関連セクション: 棒グラフ

問題2ではプレイステーションシリーズが上位を占めていました。 では、ソニー製のプラットフォームに絞って、より詳細に比較してみましょう。

  • PF2MK辞書を用いて、df_pkg_pfにメーカー名を付与してください

  • ソニー製のプラットフォームのみに絞り込んでください

  • プラットフォームごとの合計パッケージ数を横棒グラフで可視化してください

Hide code cell source
# メーカー名列の付与(プラットフォーム名からマッピング)
df_pkg_pf["maker"] = df_pkg_pf["pfname"].map(PF2MK)

# ソニー製プラットフォームのみに絞り込み
df_sony = df_pkg_pf[df_pkg_pf["maker"] == "ソニー"].copy()

# プラットフォームごとの合計パッケージ数を集計
df_sony_agg = df_sony.groupby("pfname")["pkgid"].nunique().reset_index(name="n_pkg")

# パッケージ数の多い順にソート
df_sony_agg = df_sony_agg.sort_values("n_pkg", ascending=False, ignore_index=True)

# 可視化用にカラム名を変更
df_sony_agg = df_sony_agg.rename(
    columns={"pfname": "プラットフォーム名", "n_pkg": "パッケージ数"}
)

# 横棒グラフで可視化
fig = px.bar(
    df_sony_agg, y="プラットフォーム名", x="パッケージ数", orientation="h", height=300
)

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

解説

.map()メソッドで辞書を使ってプラットフォーム名からメーカー名に変換し、ブールインデックスでソニー製品のみに絞り込みました。

プレイステーション2が最もパッケージ数が多く、次いでプレイステーション、PSPと続いています。 据置機と携帯機の両方でソニーが強力なプラットフォームを持っていたことがわかります。

関連セクション: 詳しくは棒グラフを参照してください。

標準 問題4:2000年代のマンガ作者別合計話数#

関連セクション: 積上げ棒グラフ

本書ではマンガ作者別の合計話数を年代別に可視化しました。 ここでは、2000年代(2000〜2009年)に絞って、どの作者が最も多くの話数を持っていたか確認してみましょう。

  • df_ceに年代情報を付与し、2000年代のデータのみに絞り込んでください

  • df_cc_crtとマージして、マンガ作者名を取得してください

  • マンガ作者ごとの合計話数を集計し、上位10名を横棒グラフで可視化してください

Hide code cell source
# df_ceに年情報を付与
df_ce["year"] = pd.to_datetime(df_ce["date"]).dt.year

# 2000年代(2000〜2009年)のデータのみに絞り込み
df_ce_2000s = df_ce[(df_ce["year"] >= 2000) & (df_ce["year"] < 2010)].copy()

# マンガ作品ごとの話数を集計
df_cc = df_ce_2000s.groupby("ccid")["ceid"].nunique().reset_index(name="n_ce")

# マンガ作品と作者の紐付け情報とマージ
df_cm = pd.merge(df_cc, df_cc_crt[["ccid", "crtname"]], on="ccid", how="left")

# マンガ作者ごとの合計話数を集計
df_cm_agg = df_cm.groupby("crtname")["n_ce"].sum().reset_index()

# 上位10名を抽出
df_cm_agg = df_cm_agg.sort_values("n_ce", ascending=False, ignore_index=True).head(10)

# 可視化用にカラム名を変更
df_cm_agg = df_cm_agg.rename(columns={"crtname": "マンガ作者名", "n_ce": "合計話数"})

# 横棒グラフで可視化
fig = px.bar(df_cm_agg, y="マンガ作者名", x="合計話数", orientation="h", height=400)

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

解説

日付から年を抽出し、2000年代に絞り込んでからマンガ作者別に集計しました。

2000年代は秋本治(こち亀)や水島新司(ドカベン等)など、長期連載作品を持つ作者が上位に並んでいます。 特定の年代に絞ることで、その時代のマンガ業界の傾向を把握できます。

関連セクション: 詳しくは積上げ棒グラフを参照してください。

発展 問題5:メーカー別ゲームパッケージ数#

関連セクション: 棒グラフ

本書ではプラットフォームごとのパッケージ数を確認しました。 では、プラットフォームを「メーカー」という新しい切り口で再集計すると、結果はどう変わるでしょうか?

  • gm_pkg_pf.csv を読み込み、プラットフォーム名からメーカー名へのマッピング(PF2MK)を適用してください

  • メーカー別の合計パッケージ数を集計してください

  • 横棒グラフで可視化してください

Hide code cell source
# メーカー名列の付与(プラットフォーム名からマッピング)
df_pkg_pf["maker"] = df_pkg_pf["pfname"].map(PF2MK)

# メーカーごとにユニークなパッケージID(pkgid)を集計
df_maker_counts = (
    df_pkg_pf.groupby("maker")["pkgid"].nunique().reset_index(name="n_pkg")
)

# パッケージ数の多い順にソート
df_maker_counts = df_maker_counts.sort_values("n_pkg", ascending=False)

# 横棒グラフで可視化
fig = px.bar(
    df_maker_counts,
    y="maker",
    x="n_pkg",
    orientation="h",
    labels={"maker": "メーカー名", "n_pkg": "合計パッケージ数"},
)

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

解説

元データから「メーカー」という新しい切り口を定義し、集計するプロセスを体験する問題です。

単一の質的変数の量を比較する際は、横棒グラフを用いると長いカテゴリ名を無理なく配置できます。 また、数値順に並べることで、オーディエンスの認知的な負荷を下げることが期待できます。

関連セクション: 詳しくは棒グラフを参照してください。

発展 問題6:アニメシリーズの放送枠#

関連セクション: 積上げ棒グラフ

長寿アニメ作品は、その歴史の中で決まった曜日に放送される「放送枠」を持っていることがあります。 この様子を積上げ棒グラフで可視化してみましょう。

  • an_ae.csv を読み込み、放送曜日を抽出してください

  • アニメシリーズごとの合計放送日数を「放送曜日(月〜日)」で色分けした積上げ横棒グラフを作成してください

  • 合計放送日数が多い上位10シリーズを対象とし、放送日数の多い順に並べてください

Hide code cell source
# 放送曜日の抽出
df_ae["date"] = pd.to_datetime(df_ae["date"])
df_ae["weekday"] = df_ae["date"].dt.weekday

# アニメシリーズ(asid)と曜日(weekday)ごとに放送日数(dateのユニーク数)を集計
df_an_agg = (
    df_ae.groupby(["asid", "weekday"])["date"].nunique().reset_index(name="days")
)

# シリーズ全体の合計放送日数を計算し、上位10シリーズを特定
total_days = df_an_agg.groupby("asid")["days"].sum().sort_values(ascending=False)
top_asids = total_days.head(10).index

# 上位10件に絞り込み、合計日数の多い順にソート(Categorical型を用いて順序を固定)
df_an_top = df_an_agg[df_an_agg["asid"].isin(top_asids)].copy()
df_an_top["asid"] = pd.Categorical(
    df_an_top["asid"], categories=top_asids, ordered=True
)
# 曜日情報を紐づけ
df_an_top["yobi"] = df_an_top["weekday"].map(WEEKDAY2YOBI)

# 代表的な作品名の紐付け
asid2name = df_ae.sort_values("date").groupby("asid")["acname"].first().to_dict()
df_an_top["asname"] = df_an_top["asid"].map(asid2name)

# Y軸の表示順を合計放送日数の降順で定義
top_names_ordered = [asid2name[aid] for aid in top_asids[::-1]]
# 曜日の表示順も定義
yobi_ordered = [yobi for yobi in WEEKDAY2YOBI.values()]

# 積上げ横棒グラフで可視化
fig = px.bar(
    df_an_top,
    y="asname",
    x="days",
    color="yobi",
    orientation="h",
    barmode="stack",
    category_orders={"asname": top_names_ordered, "yobi": yobi_ordered},
    color_discrete_sequence=OKABE_ITO,
    labels={
        "asname": "代表的な作品名",
        "days": "合計放送日数",
        "yobi": "放送曜日",
    },
)
fig.update_layout(legend=dict(yanchor="top", y=0.99, xanchor="right", x=0.99))

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

解説

積上げ棒グラフを用いて、アニメシリーズと放送曜日という二つの質的変数の組み合わせの を表現する問題です。

『忍たま乱太郎』や『おじゃる丸』のように平日に毎日放送される帯番組と、特定の週末のみに放送される作品では「色の構成」に明確な違いが見られます。 ここから、各作品のターゲット層や放送形態を推察できます。

関連セクション: 詳しくは積上げ棒グラフを参照してください。

発展 問題7:ゲーム発売曜日の変遷#

関連セクション: ヒートマップ

ゲーム業界には「新作は〇曜日に出るもの」という商習慣があると言われています。 時代とともに「発売曜日の定番」がどのように変化してきたか、ヒートマップで全体像を俯瞰しましょう。

  • gm_pkg_pf.csv を利用して、発売年と発売曜日を抽出してください

  • 発売年と発売曜日(月〜日)の組み合わせにおけるパッケージ数をヒートマップで可視化してください

Hide code cell source
# 発売年、発売曜日情報の付与
df_pkg_pf["date"] = pd.to_datetime(df_pkg_pf["date"])
df_pkg_pf["year"] = df_pkg_pf["date"].dt.year
df_pkg_pf["weekday"] = df_pkg_pf["date"].dt.weekday

# 年代と曜日ごとにユニークなパッケージ数を集計
df_hm = (
    df_pkg_pf.groupby(["year", "weekday"])["pkgid"].nunique().reset_index(name="count")
)
df_hm["yobi"] = df_hm["weekday"].map(WEEKDAY2YOBI)

# 曜日の表示順を定義
yobi_ordered = [yobi for yobi in WEEKDAY2YOBI.values()]

# ヒートマップで可視化
fig = px.density_heatmap(
    df_hm,
    x="year",
    y="yobi",
    z="count",
    category_orders={"yobi": yobi_ordered},
    labels={"year": "発売年", "yobi": "発売曜日", "count": "パッケージ数"},
    color_continuous_scale="Plasma",
    nbinsx=df_hm["year"].nunique(),
)

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

解説

ヒートマップは、2つの質的変数の組み合わせによる「パターンの濃淡」を俯瞰するのに最適です。

このグラフを見ると、日本のゲーム市場における発売曜日の定番が曜日であることが一目でわかrます。 また、1990年代には曜日にも一定のボリュームがあったものの、1997年ごろを境に曜日にシフトした様子も確認できます。

関連セクション: 詳しくはヒートマップを参照してください。

応用 問題8:アニメ放送枠の年代別推移#

関連セクション: ヒートマップ

問題7ではゲームの発売曜日の変遷を確認しました。 同様に、アニメの「放送枠」が年代とともにどのように変化してきたか、ヒートマップで俯瞰してみましょう。

ただし、アニメ各話(aeid)の振り方には作品によって違いがあります。 例えば、30分枠で2話を放送する場合、1つのaeidでまとめて表現される作品と、2つのaeidで別々に表現される作品があります。 そこで、同一日・同一作品の放送を「1枠」として統一的に扱うことにします。

  • df_aeを用いて、1990年以降のデータに絞り込んでください

  • 放送年と放送曜日を抽出してください

  • 同一日(date)・同一作品(acid)の組み合わせを「1枠」として、放送年×放送曜日ごとの放送枠数を集計してください

  • ヒートマップで可視化してください

Hide code cell source
# 放送年と放送曜日を抽出(df_aeのdate列は既にdatetime型に変換済み)
df_ae["year"] = df_ae["date"].dt.year
df_ae["weekday"] = df_ae["date"].dt.weekday

# 1990年以降のデータに絞り込み
df_ae_1990 = df_ae[df_ae["year"] >= 1990].copy()

# 同一日・同一作品の組み合わせを「1枠」として扱うため、重複を除去
# date, acid, year, weekdayの組み合わせでユニークなレコードを抽出
df_slots = df_ae_1990[["date", "acid", "year", "weekday"]].drop_duplicates()

# 放送年と放送曜日ごとに放送枠数を集計
df_hm_an = df_slots.groupby(["year", "weekday"]).size().reset_index(name="n_slots")

# 曜日名を付与
df_hm_an["yobi"] = df_hm_an["weekday"].map(WEEKDAY2YOBI)

# 曜日の表示順を定義
yobi_ordered = list(WEEKDAY2YOBI.values())

# ヒートマップで可視化
fig = px.density_heatmap(
    df_hm_an,
    x="year",
    y="yobi",
    z="n_slots",
    category_orders={"yobi": yobi_ordered},
    labels={"year": "放送年", "yobi": "放送曜日", "n_slots": "放送枠数"},
    color_continuous_scale="Plasma",
    nbinsx=df_hm_an["year"].nunique(),
)

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

解説

drop_duplicates()を用いて、同一日・同一作品の放送を「1枠」として統一的に扱いました。 これにより、aeidの振り方の違いによる影響を吸収しています。

ヒートマップを見ると、年代によってアニメ放送枠の分布が変化していることがわかります。 特に、2008年の曜日が目立ちますが、この枠にはどのようなアニメ作品が含まれているのでしょうか? 興味のある方は調べてみましょう。

関連セクション: 詳しくはヒートマップを参照してください。