下巻 第5章 解答例#
ここでは、 本書の学習内容の定着 を目的とした練習問題とその解答・解説を掲載します。 なお、問題の性質上、本書で取り上げた処理と重複することがあります。 ご了承ください。
前提#
以下のように、ライブラリのインポートと変数の定義が完了していることを前提とします。
Show code cell content
# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
from pathlib import Path
# pandas:データ解析ライブラリのインポート
# pdという名前で参照可能
import pandas as pd
Show code cell content
# 読み込み対象ディレクトリの定義
# マンガデータの中間出力ファイルが格納されているディレクトリのパス
DIR_CM = Path("../../../data/cm/interim")
# アニメデータの中間出力ファイルが格納されているディレクトリのパス
DIR_AN = Path("../../../data/an/interim")
# 外部データソースから作成したファイルを格納しているディレクトリのパス
# (今回は、事前に作成したマンガ作品とアニメ作品の紐づけファイル)
DIR_EXTERNAL = Path("../../../data/mix/external")
# メディア展開データが保存されているディレクトリのパス
DIR_IN = Path("../../../data/mix/input")
Show code cell content
# アニメ作品の結合用データのファイル名を定義
FN_AC_MERGE = "ac_merge.csv"
# マンガ作品の結合用データのファイル名を定義
FN_CC_MERGE = "cc_merge.csv"
# アニメ作品とマンガ作品の対応関係に関するデータのファイル名を定義
FN_AC_CC = "ac_cc.csv"
# アニメ各話と原作マンガの作者者の対応関係に関するファイル
FN_AE_CRT = "mix_ae_crt.csv"
# マンガ各話とアニメ作品の対応関係に関するファイル
FN_CE_AC = "mix_ce_ac.csv"
また、以下のようにファイルを読み込んでいると仮定します。
Show code cell content
# フィルタリング前の「生」の対応表を読み込む
df_ac_cc = pd.read_csv(DIR_EXTERNAL / FN_AC_CC)
# アニメ作品データ(放送日の比較用)
df_ac_merge = pd.read_csv(DIR_AN / FN_AC_MERGE)
# マンガ作品データ(掲載日の比較用)
df_cc_merge = pd.read_csv(DIR_CM / FN_CC_MERGE)
# アニメ各話とマンガ作者の対応関係
df_mix_ae_crt = pd.read_csv(DIR_IN / FN_AE_CRT)
# マンガ各話とアニメ作品の対応関係
df_mix_ce_ac = pd.read_csv(DIR_IN / FN_CE_AC)
# 日付列をdatetime型に変換
df_ac_merge["first_date"] = pd.to_datetime(df_ac_merge["first_date"])
df_cc_merge["first_date"] = pd.to_datetime(df_cc_merge["first_date"])
基礎 問題1:対応表の件数確認#
関連セクション: メディア展開データの前処理
本章で使用する対応表df_ac_ccは、アニメ作品ID(acid)とマンガ作品ID(ccid)の紐づけを管理しています。
shape属性とnunique()メソッドを用いて、対応表の行数と各IDのユニーク数を確認してください。
行数とユニーク数の関係から、acidとccidの対応関係にはどのような特徴があるかを考察しましょう。
ヒント
df.shapeでDataFrameの行数と列数を確認できますdf["列名"].nunique()でユニークな値の数を取得できます
Show code cell content
# 対応表の行数を確認
n_rows = df_ac_cc.shape[0]
print(f"行数: {n_rows}")
# acidのユニーク数を確認
n_acid = df_ac_cc["acid"].nunique()
print(f"ユニークなacid数: {n_acid}")
# ccidのユニーク数を確認
n_ccid = df_ac_cc["ccid"].nunique()
print(f"ユニークなccid数: {n_ccid}")
# 対応関係の特徴を出力
print(f"\n→ acidは重複なし(行数={n_rows}とacid数={n_acid}が一致)")
print(f"→ ccidには重複あり(1つのマンガに複数のアニメが対応)")
行数: 360
ユニークなacid数: 360
ユニークなccid数: 229
→ acidは重複なし(行数=360とacid数=360が一致)
→ ccidには重複あり(1つのマンガに複数のアニメが対応)
解説
shape属性とnunique()メソッドを組み合わせることで、対応表の基本的な構造を把握できます。
行数とユニーク数が一致する列(acid)は主キーとして機能しており、一致しない列(ccid)は重複があることを意味します。
この場合、1つのマンガ作品から複数のアニメ作品が派生している(例:シリーズ続編、リメイク作品)ことがわかります。
関連セクション: 詳しくはメディア展開データの前処理を参照してください。
基礎 問題2:メディア展開データの形状確認#
関連セクション: メディア展開データの基礎分析
本文ではdf_mix_ae_crtの形状をshapeで確認しました。ここでは、もう一つのメディア展開データdf_mix_ce_acについて同様の確認を行いましょう。
df_mix_ce_acのshape属性を用いて行数と列数を確認し、head(10)で先頭10行を表示してください。
ヒント
df.shapeでDataFrameの行数と列数を確認できますdf.head(10)で先頭10行を表示できます
Show code cell content
# データフレームの形状(行数, 列数)を確認
print(f"df_mix_ce_ac の形状: {df_mix_ce_ac.shape}")
# 先頭10行を表示
df_mix_ce_ac.head(10)
df_mix_ce_ac の形状: (181681, 25)
| ceid | cename | ccid | miid | page_start | page_end | pages | page_start_position | two_colored | four_colored | ... | first_date_cc | last_date_cc | mcid | mcname | acid | acname | asid | n_ae | first_date_ac | last_date_ac | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | CE00000 | 第238話/この世代 | C90829 | M535428 | 10.0 | 31.0 | 22.0 | 0.021368 | False | True | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 1 | CE00026 | 第237話/トーナメント | C90829 | M535429 | 125.0 | 144.0 | 20.0 | 0.238550 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 2 | CE00062 | 第236話/絆 | C90829 | M535430 | 223.0 | 242.0 | 20.0 | 0.478541 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 3 | CE00086 | 第235話/指先から… | C90829 | M535431 | 183.0 | 204.0 | 22.0 | 0.405765 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 4 | CE00112 | 第234話/何にも出来ないワケじゃない | C90829 | M535432 | 221.0 | 240.0 | 20.0 | 0.472222 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 5 | CE00135 | 第233話/Just do it | C90829 | M535433 | 129.0 | 148.0 | 20.0 | 0.269311 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 6 | CE00155 | 第232話 SHINE ON | C90829 | M535434 | 9.0 | 34.0 | 26.0 | 0.020737 | False | True | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 7 | CE00194 | 第231話/道しるべ | C90829 | M535435 | 225.0 | 244.0 | 20.0 | 0.497788 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 8 | CE00210 | 第230話/継承 | C90829 | M535436 | 59.0 | 78.0 | 20.0 | 0.129956 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
| 9 | CE00250 | 第229話/イメージ | C90829 | M535437 | 311.0 | 330.0 | 20.0 | 0.654737 | False | False | ... | 2006-05-31 | 2016-01-08 | C119033 | 週刊少年マガジン | C14866 | ダイヤのA[エース] | C5641 | 75.0 | 2013-10-06 | 2015-03-29 |
10 rows × 25 columns
解説
shape属性とhead()メソッドを組み合わせることで、データフレームの概要を素早く把握できます。
df_mix_ce_acはマンガ各話(ceid)とアニメ作品(acid)の紐づけを管理するデータで、本文で確認したdf_mix_ae_crtとは異なる視点(マンガ話ベース vs アニメ話ベース)のデータです。
両者の行数や含まれる列を比較することで、メディア展開データの構造をより深く理解できます。
関連セクション: 詳しくはメディア展開データの基礎分析を参照してください。
標準 問題3:特定雑誌に絞った統計#
関連セクション: メディア展開データの基礎分析
本書では、groupby("mcname")を用いて全雑誌を対象にアニメ化実績を集計しました。
今回は、週刊少年サンデーに絞り込んで、アニメ化された作品数と関わった作者数を集計してみましょう。
df_mix_ae_crtから週刊少年サンデー(mcname列が"週刊少年サンデー")のデータのみを抽出し、acid(アニメ作品)とcrtid(マンガ作者)のユニーク数を確認してください。
ヒント
ブールインデックス
df[df["列名"] == "値"]でデータを絞り込めます絞り込んだDataFrameに対して
nunique()を適用できます
Show code cell content
# 週刊少年サンデーのデータのみに絞り込み
df_sunday = df_mix_ae_crt[df_mix_ae_crt["mcname"] == "週刊少年サンデー"]
# アニメ作品のユニーク数を集計
n_ac = df_sunday["acid"].nunique()
print(f"週刊少年サンデー原作のアニメ作品数: {n_ac}")
# マンガ作者のユニーク数を集計
n_crt = df_sunday["crtid"].nunique()
print(f"週刊少年サンデー原作のマンガ作者数: {n_crt}")
週刊少年サンデー原作のアニメ作品数: 56
週刊少年サンデー原作のマンガ作者数: 34
解説
ブールインデックスによるフィルタリングとnunique()を組み合わせた集計パターンです。
特定の条件に絞り込んで統計を確認することで、雑誌ごとの特徴を把握できます。 全体の傾向と比較することで、各雑誌の編集方針の違いが見えてくるかもしれません。
関連セクション: 詳しくはメディア展開データの基礎分析を参照してください。
発展 問題4:マンガ作品のカバー率#
関連セクション: メディア展開データの基礎分析
問題7(後述)では、アニメ作品(acid)の観点から前処理による情報欠落を分析します。
ここでは、 マンガ作品(ccid) の観点から同様の分析を行ってみましょう。
生の対応表(df_ac_cc)に含まれるマンガ作品数と、最終的な分析用データ(df_mix_ae_crt)に残ったマンガ作品数を比較し、マンガ作品の「残存率」を算出してください。
結果はpd.Seriesを用いて構造化されたサマリーとして出力しましょう。
ヒント
df["ccid"].nunique()でユニークなマンガ作品数を取得できます残存率は「残った数 / 元の数 × 100」で計算できます
pd.Series([値1, 値2, ...], index=[ラベル1, ラベル2, ...])でサマリーを作成できます
Show code cell content
# 生の対応表に含まれるユニークなマンガ作品数(ccidの数)
n_total_cc = df_ac_cc["ccid"].nunique()
# 最終データに残ったユニークなマンガ作品数
n_remained_cc = df_mix_ae_crt["ccid"].nunique()
# どの程度の割合が残ったかを計算(%)
coverage_cc = (n_remained_cc / n_total_cc) * 100
# 集計結果をPandasのSeriesにまとめ、名前を付けて出力
summary_cc = pd.Series(
[n_total_cc, n_remained_cc, f"{coverage_cc:.2f}%"],
index=["生の対応付けマンガ作品数", "分析対象として残ったマンガ作品数", "残存率 (%)"],
name="マンガ作品の情報欠落サマリー"
)
# summary_ccを表示
summary_cc
生の対応付けマンガ作品数 229
分析対象として残ったマンガ作品数 157
残存率 (%) 68.56%
Name: マンガ作品の情報欠落サマリー, dtype: object
解説
問題7(後述)ではアニメ作品(acid)の観点からカバー率を計算しますが、ここではマンガ作品(ccid)の観点から分析しています。
アニメ作品のカバー率とマンガ作品のカバー率は必ずしも一致しません。 理由の一つとして、1つのマンガ作品から複数のアニメ作品が派生していることが考えられます。 複数の観点からカバー率を確認することで、前処理による情報欠落の影響をより多角的に把握できます。
関連セクション: 詳しくはメディア展開データの基礎分析を参照してください。
発展 問題5:対応関係の仕様チェック#
関連セクション: メディア展開データの基礎分析
本書の仕様説明において、Aさんは「一つのアニメ作品に対して、複数の原作マンガ作品を紐づけない」というルールを定義しました。
つまり、アニメ作品ID(acid)に対してマンガ作品ID(ccid)は一意に定まる(N対1の関係)必要があります。
本書でも登場したassert文を活用して、df_ac_cc内の一つのacidに対して複数のccidが紐づいている 仕様違反 がないことを検証してください。
ヒント
グループごとのユニーク数は
groupbyとnunique()で取得できますassert文で条件を検証し、違反時にエラーメッセージを表示できます最大値の取得には
max()を使用します
Show code cell content
# acidごとにccidのユニークな数(nunique)をカウントし、その最大値を取得
max_cc_per_ac = df_ac_cc.groupby("acid")["ccid"].nunique().max()
# 最大値が1であることをassert文で検証(1を超えている場合はAssertionErrorが発生する)
assert max_cc_per_ac == 1, f"仕様違反:1つのアニメに複数の原作が紐づいています(最大 {max_cc_per_ac} 件)"
# エラーが出なければ合格
print("N:1の仕様チェックを通過しました。")
N:1の仕様チェックを通過しました。
解説
データ結合を行う前に、主キーの関係性が設計通りであるかを確認する重要なステップです。 Aさんが立てた「一つのアニメには一つの原作」というポリシーが、手作業の過程で崩れていないかをプログラムで担保しています。 このような整合性チェックを自動化しておくことで、後続の分析でデータの二重計上などのバグを防ぐことができます。
関連セクション: 詳しくはメディア展開データの基礎分析を参照してください。
発展 問題6:時間的整合性の検証#
関連セクション: メディア展開データの基礎分析
アニメ作品の放送開始日は、必ず原作マンガの掲載開始日以降(同日を含む)となるよう対応表を作成しました。
もしアニメが先に始まっているデータが存在する場合、それは原作の紐づけ誤りや日付データの不備を示唆しています。
アニメとマンガの開始日(first_date)をマージし、全てのアニメ作品において「マンガ開始日 <= アニメ開始日」が成立していることを、assert文を用いて検証しましょう。
ヒント
複数のDataFrameの結合には
pd.merge()を使用します日付の差分は
.dt.daysで日数として取得できます全要素が条件を満たすかは
.all()で確認できますassert文で条件を検証できます
Show code cell content
# 対応表にアニメの開始日をマージ(first_date_acとする)
df_check_date = pd.merge(df_ac_cc, df_ac_merge[["acid", "first_date"]], on="acid", how="inner").rename(columns={"first_date": "first_date_ac"})
# さらにマンガの開始日をマージ(first_date_ccとする)
df_check_date = pd.merge(df_check_date, df_cc_merge[["ccid", "first_date"]], on="ccid", how="inner").rename(columns={"first_date": "first_date_cc"})
# 日付の差分(アニメ開始日 - マンガ開始日)を計算し、days(日)単位で取得
df_check_date["diff_days"] = (df_check_date["first_date_ac"] - df_check_date["first_date_cc"]).dt.days
# 全てのアニメ放送開始がマンガ掲載開始以降(0日以上)であることを検証
assert (df_check_date["diff_days"] >= 0).all(), "仕様違反:マンガ掲載よりも早くアニメ放送が開始されている作品があります"
# エラーが出なければ合格
print("時間的整合性のチェックを通過しました。")
時間的整合性のチェックを通過しました。
解説
データの論理的な矛盾をドメイン知識でチェックする問題です。 本文で触れた『アソボット戦記 五九』のように、メディアミックス作品では開始日が非常に近いことがありますが、あまりにアニメが先行している場合は「原作の取り違え」や「初出情報の誤り」の可能性[1]が浮上します。 基礎分析の段階でこうした時間軸の矛盾を洗い出すことは、データの信頼性を「物語」の側面から検証する行為に他なりません。
関連セクション: 詳しくはメディア展開データの基礎分析を参照してください。
発展 問題7:マージによる情報欠落#
関連セクション: メディア展開データの基礎分析
前処理の過程で、放送日の欠損などを理由に除外されたアニメ作品が存在します。
生の対応表(df_ac_cc)に含まれる作品数と、最終的な分析用データ(mix_ae_crt.csv)に残った作品数を比較し、データの「残存率」を算出しましょう。
結果は単なる数値の表示ではなく、PandasのSeries型を用いて構造化されたサマリーとして出力してください。
ヒント
ユニークな件数は
nunique()で取得できますカバー率(残存率)は「残った数 / 元の数 × 100」で計算できます
pd.Series()で構造化されたサマリーを作成できます
Show code cell content
# 生の対応表に含まれるユニークな作品数(acidの数)
n_total = df_ac_cc["acid"].nunique()
# 最終データに残ったユニークな作品数
n_remained = df_mix_ae_crt["acid"].nunique()
# どの程度の割合が残ったかを計算(%)
coverage = (n_remained / n_total) * 100
# 集計結果をPandasのSeriesにまとめ、名前を付けて出力
summary_report = pd.Series(
[n_total, n_remained, f"{coverage:.2f}%"],
index=["生の対応付け作品数", "分析対象として残った作品数", "残存率 (%)"],
name="前処理による情報欠落のサマリー"
)
# summary_reportを表示
summary_report
生の対応付け作品数 360
分析対象として残った作品数 238
残存率 (%) 66.11%
Name: 前処理による情報欠落のサマリー, dtype: object
解説
前処理によるデータの選り分けの影響を探る分析です。
本文で『うる星やつら』などが除外されてしまった背景には、放送日情報の欠損がありました。 最終的な可視化結果を見る際、この「欠落したデータ」の存在を意識できるかどうかが、分析の質を左右するポイントです。
関連セクション: 詳しくはメディア展開データの基礎分析を参照してください。