上巻 第5章 解答例#
ここでは、 本書の学習内容の定着 を目的とした練習問題とその解答・解説を掲載します。 なお、問題の性質上、本書で取り上げた処理と重複することがあります。 ご了承ください。
前提#
以下のように、ライブラリのインポートと変数の定義が完了していることを前提とします。
Show 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
Show code cell content
# データ格納ディレクトリのパス
DIR_IN = Path("../../../data/cm/input")
# ファイル名の定義
FN_CE = "cm_ce.csv"
# 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
# マンガ各話データを格納するcm_ce.csvを読み出し、df_ceとして格納
df_ce = pd.read_csv("../../../data/cm/input/cm_ce.csv")
基礎 問題1:DataFrameの冒頭行の確認#
関連セクション: Pandasの基礎
データ分析の第一歩は、データの中身を確認することです。
本文では head() メソッドでデフォルトの5行を表示しましたが、もう少し多くの行を確認してみましょう。
df_ce の先頭10行を表示してください。
ヒント
head()メソッドに引数を渡すことで、表示する行数を指定できます例えば
df.head(3)とすると先頭3行が表示されます
Show 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_ce の pages(ページ数)列と page_start_position(掲載位置)列に対して describe() メソッドを適用し、記述統計量を確認してください。
ヒント
複数の列を選択するには
df[["列名1", "列名2"]]のようにリストで指定しますdescribe()メソッドは、件数・平均・標準偏差・最小値・四分位数・最大値を一括で算出します
Show 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() で確認してください。
ヒント
特定の条件に合致する行を抽出するには、ブールインデックス
df[df["列名"] == "値"]を使用します例えば
df[df["mcname"] == "週刊少年サンデー"]とすると、週刊少年サンデーのデータのみが抽出されます
Show 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行を表示してください。
ヒント
条件による抽出は
df[df["列名"] >= 値]のようにブールインデックスを使用しますソートには
sort_values("列名")を使用します(デフォルトは昇順)先頭5行の表示は
head()を使用します
Show 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)データのみを抽出してください抽出後のデータの平均ページ数を算出し、全体像を確認しましょう
ヒント
条件による行の抽出にはブールインデックスを使用します(例:
df[df["col"] > 3])平均値の算出には
.mean()メソッドを使用します
Show 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冊あたりの平均作品数」を算出して比較しましょう
ヒント
複数のカラムでグループ化するには
.groupby(["col1", "col2"])とリスト形式で指定しますユニークな要素数は
.nunique()でカウントできます集計後の列名は
.rename(columns={...})で変更できます
Show 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_colにmcnameを指定して、雑誌ごとに分布の形状を比較してください
ヒント
px.histogram()でヒストグラムを作成しますfacet_col引数でカテゴリごとにグラフを分割できますfacet_col_wrapで折り返しの列数を指定できます
Show 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の基礎を参照してください。