上巻 第5章 解答例#

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

前提#

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

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

# 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_IN = Path("../../../data/cm/input")

# ファイル名の定義
FN_CE = "cm_ce.csv"

# plotlyの描画設定の定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"

また、本書中で取り上げた以下の関数も、同様に利用可能とします。

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
# マンガ各話データを格納するcm_ce.csvを読み出し、df_ceとして格納
df_ce = pd.read_csv("../../../data/cm/input/cm_ce.csv")

基礎 問題1:DataFrameの冒頭行の確認#

関連セクション: Pandasの基礎

データ分析の第一歩は、データの中身を確認することです。 本文では head() メソッドでデフォルトの5行を表示しましたが、もう少し多くの行を確認してみましょう。

df_ce の先頭10行を表示してください。

Hide code cell content
# df_ceの先頭10行を表示
df_ce.head(10)
ceid cename ccid miid page_start page_end pages page_start_position two_colored four_colored miname mcid mcname date price ccname
0 CE00000 第238話/この世代 C90829 M535428 10.0 31.0 22.0 0.021368 False True 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 ダイヤのA
1 CE00001 #134 話の続き C90482 M535428 33.0 50.0 18.0 0.070513 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 君のいる町
2 CE00002 第5話 チア・ザ・マシンガン! C90297 M535428 51.0 68.0 18.0 0.108974 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 アゲイン!!
3 CE00003 第233話 妖精の輝き C89978 M535428 69.0 88.0 20.0 0.147436 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 FAIRY TAIL
4 CE00004 -BOUT 71- From Dark Zone C89929 M535428 89.0 108.0 20.0 0.190171 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 A-BOUT!
5 CE00005 第94話 C90168 M535428 109.0 130.0 22.0 0.232906 False True 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 我間乱 ~GAMARAN~
6 CE00006 第36話 星 C89936 M535428 131.0 150.0 20.0 0.279915 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 AKB49 ~恋愛禁止条例~
7 CE00007 #164 どうやって C89939 M535428 151.0 168.0 18.0 0.322650 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 Baby Steps ベイビーステップ
8 CE00008 code:133 覚悟の証 C89961 M535428 169.0 188.0 20.0 0.361111 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 CODE:BREAKER コード:ブレイカー
9 CE00009 324時間目 明かされた秘密!! C91386 M535428 189.0 206.0 18.0 0.403846 False False 週刊少年マガジン 2011年 表示号数24 C119033 週刊少年マガジン 2011-05-25 248.0 魔法先生 ネギま! MAGISTER NEGI MAGI

解説

head() メソッドは、DataFrameの先頭から指定した行数を取得する基本的なメソッドです。

引数を省略するとデフォルトで5行が表示されますが、head(10) のように引数を指定することで任意の行数を表示できます。 データの概要を把握する際に、5行では不十分な場合に活用しましょう。

関連セクション: 詳しくはPandasの基礎を参照してください。

基礎 問題2:記述統計量の確認#

関連セクション: Pandasの基礎

数値データの全体像を把握するには、平均値や標準偏差などの記述統計量を確認することが有効です。

df_cepages(ページ数)列と page_start_position(掲載位置)列に対して describe() メソッドを適用し、記述統計量を確認してください。

Hide code cell content
# pages列とpage_start_position列の記述統計量を確認
df_ce[["pages", "page_start_position"]].describe()
pages page_start_position
count 180076.000000 180076.000000
mean 18.524906 0.514882
std 7.713260 0.283149
min 1.000000 0.002045
25% 17.000000 0.274943
50% 19.000000 0.520642
75% 20.000000 0.759642
max 487.000000 1.000000

解説

describe() メソッドは、数値データの記述統計量を一括で算出する便利なメソッドです。

出力には、データ件数(count)、平均値(mean)、標準偏差(std)、最小値(min)、四分位数(25%、50%、75%)、最大値(max)が含まれます。 これらの統計量を確認することで、データの分布や外れ値の有無を素早く把握できます。

関連セクション: 詳しくはPandasの基礎を参照してください。

標準 問題3:特定の雑誌に絞った基礎統計#

関連セクション: Pandasの基礎

全体の統計量だけでなく、特定のカテゴリに絞った統計量を確認することで、より詳細な傾向が見えてきます。

df_ce から週刊少年チャンピオン(mcname が「週刊少年チャンピオン」)のデータのみを抽出し、pages 列の記述統計量を describe() で確認してください。

Hide code cell content
# 週刊少年チャンピオンのデータのみを抽出
df_champion = df_ce[df_ce["mcname"] == "週刊少年チャンピオン"]

# pages列の記述統計量を確認
df_champion["pages"].describe()
count    45444.000000
mean        18.076886
std          7.012900
min          1.000000
25%         16.000000
50%         20.000000
75%         20.000000
max        207.000000
Name: pages, dtype: float64

解説

ブールインデックスによるフィルタリングと describe() を組み合わせることで、特定のカテゴリに限定した統計量を確認できます。

週刊少年チャンピオンのページ数統計を全体と比較すると、雑誌ごとの編集方針の違いが見えてくるかもしれません。 このように、データを適切に分割して分析することは、データ分析の基本的なアプローチです。

関連セクション: 詳しくはPandasの基礎を参照してください。

標準 問題4:特定の条件で絞ったソート#

関連セクション: Pandasの基礎

データをソートすることで、特定の傾向を持つデータを見つけやすくなります。 フィルタリングとソートを組み合わせて、特定の条件を満たすデータを確認してみましょう。

df_ce からページ数(pages)が10以上のデータのみを抽出し、掲載位置(page_start_position)で昇順にソートして先頭5行を表示してください。

Hide code cell content
# ページ数が10以上のデータのみを抽出
df_10plus = df_ce[df_ce["pages"] >= 10]

# 掲載位置で昇順にソートし、先頭5行を表示
df_10plus.sort_values("page_start_position").head()
ceid cename ccid miid page_start page_end pages page_start_position two_colored four_colored miname mcid mcname date price ccname
85491 CE90522 第1話 反逆者 C94628 M556711 1.0 45.0 45.0 0.002273 False True 週刊少年チャンピオン 2001年 表示号数27 C120282 週刊少年チャンピオン 2001-06-14 219.0 スクライド
68926 CE73441 狼健在の巻 C88709 M544455 1.0 27.0 27.0 0.003175 True True 週刊少年ジャンプ 1977年 表示号数25 C119459 週刊少年ジャンプ 1977-06-20 150.0 サーキットの狼
85417 CE90448 NaN C95128 M556708 2.0 32.0 31.0 0.004608 False True 週刊少年チャンピオン 2001年 表示号数30 C120282 週刊少年チャンピオン 2001-07-05 210.0 ドカベン プロ野球編
115909 CE120940 第1話 ポム・スフレ C93562 M577379 3.0 80.0 78.0 0.005576 False True 週刊少年サンデー 2008年 表示号数30 C117607 週刊少年サンデー 2008-07-09 238.0 ★★★(三ツ星)のスペシャリテ
112518 CE117549 第4話/春の巻 4 C92222 M577240 3.0 26.0 24.0 0.005639 False True 週刊少年サンデー 2011年 表示号数22 C117607 週刊少年サンデー 2011-04-27 257.0 銀の匙 Silver Spoon

解説

ブールインデックスによるフィルタリングと sort_values() によるソートを組み合わせることで、条件を満たすデータを特定の順序で確認できます。

page_start_position が小さいほど雑誌の先頭に近い位置に掲載されています。 10ページ以上の作品で最も先頭に掲載されているものを確認することで、巻頭カラーや看板作品を抽出できる可能性があります。

関連セクション: 詳しくはPandasの基礎を参照してください。

標準 問題5:短ページ作品の除外と基礎統計#

関連セクション: Pandasの基礎

データセットの中には、数ページのみの告知や短編掲載が含まれていることがあります。 一般的なマンガ作品を分析する際は、これらを除外した方が良いでしょう。

  • pages(ページ数)列を利用して、ページ数が3ページより大きい(pages > 3)データのみを抽出してください

  • 抽出後のデータの平均ページ数を算出し、全体像を確認しましょう

Hide code cell content
# pagesが3より大きい行のみを抽出し、df_filteredとして定義
df_filtered = df_ce[df_ce["pages"] > 3]

# 抽出したデータのpages列に対してmeanメソッドを適用し、平均値を算出
avg_pages = df_filtered["pages"].mean()

# 計算された平均ページ数を表示
print(f"除外後の平均ページ数:{avg_pages:.2f}ページ")
除外後の平均ページ数:18.97ページ

解説

Pandasにおける ブールインデックス を用いたフィルタリングと、統計用メソッドの基本を確認する問題です。

df[df["列名"] > 値] という記法は、条件を満たす行のみを選択する強力な手法です。 また、.mean() メソッドは欠損値を自動的に除外して計算してくれるため、前処理後のデータの状態を素早く把握できます。

興味がある方は、 ページ数でフィルタリングしない データに対して平均ページ数を算出してみましょう。

関連セクション: 詳しくはPandasの基礎を参照してください。

発展 問題6:雑誌1冊あたりのマンガ作品数#

関連セクション: Pandasの基礎

マンガ雑誌を「物理的な1冊」として捉えたとき、そこに何作品が詰め込まれているかは、雑誌の個性や読み応えを決定づける重要な要素です。

  • 雑誌名(mcname)と雑誌巻号ID(miid)をキーにグループ化してください

  • 1冊ごとの作品数(ccidのユニーク数)を算出し、新しいデータフレーム df_counts を作成してください

  • 各雑誌(mcname)ごとの「1冊あたりの平均作品数」を算出して比較しましょう

Hide code cell content
# 雑誌名(mcname)と巻号(miid)でグループ化し、作品(ccid)のユニーク数を算出
df_counts = df_ce.groupby(["mcname", "miid"])["ccid"].nunique().reset_index()

# 集計した列名を分かりやすく「作品数」を意味する名称に変更
df_counts = df_counts.rename(columns={"ccid": "ccid_per_miid"})

# 雑誌(mcname)ごとに、1冊あたりの作品数の平均を算出
df_summary = df_counts.groupby("mcname")["ccid_per_miid"].mean().reset_index()

# 集計結果を表示
df_summary
mcname ccid_per_miid
0 週刊少年サンデー 19.878630
1 週刊少年ジャンプ 18.461839
2 週刊少年チャンピオン 19.379147
3 週刊少年マガジン 19.463605

解説

.groupby() メソッドによる多段集計と、列名の変更操作を確認する問題です。

複数のカラムをリスト形式 ["mcname", "miid"] で渡すことで、より細かい粒度でのグループ化が可能になります。 集計後に .nunique() でユニークな要素数を数えたり、.rename(columns={...}) で列名を整理したりする操作は、コードの可読性を高めるために重要です。

なお、雑誌ごとの平均マンガ作品数の推移は、上巻4章や下巻4章の リッジラインプロット で扱います。興味のある方はチェックしてみましょう。

関連セクション: 詳しくはPandasの基礎を参照してください。

発展 問題7:作品数分布#

関連セクション: Plotlyの基礎

問題6で算出した「平均値」だけでは、合併号や増刊号などで作品数が大きく変動している可能性(分布の広がり)が見えません。 df_counts を利用して、1冊あたりの作品数の分布を雑誌ごとに可視化しましょう。

  • px.histogram() を使用して、1冊あたりの作品数(ccid_per_miid)のヒストグラムを作成してください

  • facet_colmcname を指定して、雑誌ごとに分布の形状を比較してください

Hide code cell source
# px.histogramを使用して、1冊あたりの作品数の分布を可視化
# x軸にccid_per_miidを指定し、facet_colで雑誌別に分割
fig = px.histogram(
    df_counts, 
    x="ccid_per_miid", 
    facet_col="mcname", 
    facet_col_wrap=2,
    nbins=20, # 適切なビン数を設定して分布を詳細に見せる
    labels={"ccid_per_miid": "1冊あたりの作品数"}
)

# update_yaxesを用いて、すべてのサブプロットの軸ラベルを日本語に設定
# title_text引数を使用することで、facet内のすべての軸を一括で更新できる
fig.update_yaxes(title_text="巻号数(出現頻度)")

# 各ファセットのタイトルを簡略化
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

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

解説

Plotly Express(px)の 直感的なグラフ構築ファセットによる分割 を体験する問題です。

px.histogram() をはじめとする Plotly Express の関数は、少ないコード量でインタラクティブなグラフを生成できます。 特に facet_col 引数一つで、カテゴリーごとにグラフを分割して並べる処理が完結する点は、Plotly の大きなメリットです。

関連セクション: 詳しくはPlotlyの基礎を参照してください。