上巻 第6章 解答例#

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

前提#

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

Hide code cell content
# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
# 組み合わせを生成するためのitertoolsをインポート
from itertools import combinations
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_AN_IN = Path("../../../data/an/input")  # アニメデータ
DIR_GM_IN = Path("../../../data/gm/input")  # ゲームデータ

# アニメ各話に関するファイル
FN_AE = "an_ae.csv"
# アニメ作品と声優の対応関係に関するファイル
FN_AC_ACT = "an_ac_act.csv"

# ゲームパッケージとプラットフォームの対応関係に関するファイル
FN_PKG_PF = "gm_pkg_pf.csv"

以下のファイルを読み込み済みと仮定します。

Hide code cell content
df_ae = pd.read_csv(DIR_AN_IN / FN_AE)  # アニメ各話データ
df_ac_act = pd.read_csv(DIR_AN_IN / FN_AC_ACT)  # アニメ作品と声優
df_pkg_pf = pd.read_csv(DIR_GM_IN / FN_PKG_PF)  # ゲームパッケージとプラットフォーム
Hide code cell content
# 日付列をdatetime型に変換
df_ae["date"] = pd.to_datetime(df_ae["date"])
df_ac_act["first_date"] = pd.to_datetime(df_ac_act["first_date"])
df_ac_act["last_date"] = pd.to_datetime(df_ac_act["last_date"])

基礎 問題1:特定列のユニーク値数の確認#

関連セクション: ゲームデータの基礎分析

本書では df_pkg_pf.nunique() を用いて全列のユニーク値数を確認しました。 ここでは、特定の列(pfnamepublisher)に絞ってユニーク値数を確認してみましょう。

df_pkg_pf から pfname 列と publisher 列を選択し、それぞれのユニーク値数を表示してください。

Hide code cell content
# df_pkg_pfから特定の列(pfnameとpublisher)を選択
# nunique()メソッドを使用して、各列のユニーク値の数を計算
df_pkg_pf[["pfname", "publisher"]].nunique()
pfname         47
publisher    1952
dtype: int64

解説

特定の列に絞ってユニーク値数を確認することで、データに関する理解を深めることができます。

pfname(プラットフォーム名)は47種類、publisher(パブリッシャー)は約2000社存在することがわかります。 プラットフォーム数に比べてパブリッシャー数が非常に多いことから、ゲーム業界には多数の企業が参入していることが読み取れます。

関連セクション: 詳しくはゲームデータの基礎分析を参照してください。

基礎 問題2:声優データの活動期間の確認#

関連セクション: アニメデータの基礎分析

本書では df_ae["date"] 列の最小値と最大値を確認して、アニメ各話の放送期間を把握しました。 ここでは、声優データ(df_ac_act)の first_date 列と last_date 列の範囲を確認してみましょう。

df_ac_actfirst_date 列と last_date 列それぞれについて、最小値と最大値を表示してください。

Hide code cell content
# df_ac_actのfirst_date列とlast_date列の最小値・最大値を確認
# first_date: アニメ作品の最初の放送日
# last_date: アニメ作品の最後の放送日
print(
    "first_date の範囲:",
    df_ac_act["first_date"].min(),
    "〜",
    df_ac_act["first_date"].max(),
)
print(
    "last_date の範囲:",
    df_ac_act["last_date"].min(),
    "〜",
    df_ac_act["last_date"].max(),
)
first_date の範囲: 1963-01-01 00:00:00 〜 2017-10-08 00:00:00
last_date の範囲: 1965-08-16 00:00:00 〜 2017-10-15 00:00:00

解説

日付列の範囲を確認することで、データがカバーする期間を把握できます。

first_date(初回放送日)とlast_date(最終放送日)の範囲を見ると、声優データは1960年代から2017年頃までの作品をカバーしていることがわかります。 ただし、本文で学んだように、1990年以前のデータには大きな欠損があるため、この期間のデータを分析する際には注意が必要です。

関連セクション: 詳しくはアニメデータの基礎分析を参照してください。

標準 問題3:特定プラットフォームに絞った価格統計#

関連セクション: ゲームデータの基礎分析

本文では df_pkg_pf.describe() を用いて全データの価格統計を確認しました。 ここでは、特定のプラットフォーム(プレイステーション2)に絞って価格の統計を確認してみましょう。

df_pkg_pf から pfname が「プレイステーション2」のデータのみを抽出し、price 列の記述統計量を表示してください。

Hide code cell content
# df_pkg_pfからプレイステーション2のデータのみを抽出
# ブールインデックスを使用して、pfnameが「プレイステーション2」の行を選択
df_ps2 = df_pkg_pf[df_pkg_pf["pfname"] == "プレイステーション2"]

# プレイステーション2のprice列の記述統計量を表示
df_ps2["price"].describe()
count     4211.000000
mean      5888.962954
std       2759.501278
min       1500.000000
25%       3129.000000
50%       6800.000000
75%       7140.000000
max      38900.000000
Name: price, dtype: float64

解説

特定の条件でデータを絞り込んでから統計量を確認することで、より詳細な分析が可能になります。

プレイステーション2は約4,200本のゲームパッケージがあり、平均価格は約5,900円であることがわかります。 全プラットフォームの平均価格(約5,100円)と比較すると、やや高めの価格帯であることが読み取れます。

関連セクション: 詳しくはゲームデータの基礎分析を参照してください。

標準 問題4:特定年代に絞ったアニメ作品数#

関連セクション: アニメデータの基礎分析

本文で確認したように、アニメデータは1990年以前に大きな欠損があります。 ここでは、十分余裕を持たせて2000年以降のデータに絞って分析を行ってみましょう。

df_ae から2000年以降のデータのみを抽出し、年ごとのユニークなアニメ作品数(acid)を集計して、作品数が多い順に上位10件を表示してください。

Hide code cell content
# df_aeから2000年以降のデータのみを抽出
# ブールインデックスを使用して、dateの年が2000以上の行を選択
df_ae_2000 = df_ae[df_ae["date"].dt.year >= 2000]

# 年ごとのユニークなアニメ作品数(acid)を集計
# groupbyで年ごとにグループ化し、acidのユニーク数を計算
df_yearly_ac = df_ae_2000.groupby(df_ae_2000["date"].dt.year)["acid"].nunique()

# 作品数が多い順にソートして上位10件を表示
df_yearly_ac.sort_values(ascending=False).head(10)
date
2016    346
2014    332
2006    315
2007    306
2008    302
2015    301
2013    291
2009    267
2012    254
2005    245
Name: acid, dtype: int64

解説

データの欠損が大きい期間を除外してから分析することで、より信頼性の高い結果を得られます。

2000年以降に限定すると、20162014200620072008…の順にアニメ作品数が多いことがわかります。 2006年から2008年にかけて、何があったのでしょうか? 興味のある方は調べてみましょう。

関連セクション: 詳しくはアニメデータの基礎分析を参照してください。

発展 問題5:放送日データの空白期間の特定#

関連セクション: アニメデータの基礎分析

第6章で触れた通り、アニメデータには特定の期間に大規模な欠損が見られます。 単純に groupby で集計すると「データが0件の年」は結果から消えてしまい、欠損の深刻さを見落とす可能性があります。

  • データの最小年から最大年までの全ての年を含む「枠(レンジ)」を作成してください

  • それに対して df_ae の作品数を紐付けてください

  • 完全にデータが欠損している年を列挙してください。

なお、ここでは簡単のため「当該年にそもそもアニメが放送されていたかどうか(つまり0が正解かどうか)」には深入りしません。 実態は別として、 データとして アニメ作品数が0の年を抽出しましょう。

Hide code cell content
# date列から年情報を抽出し、新しいyear列を作成
df_ae["year"] = df_ae["date"].dt.year

# データに含まれる最小年と最大年を取得
min_year, max_year = int(df_ae["year"].min()), int(df_ae["year"].max())

# 全ての年を網羅するデータフレーム(枠)を作成
df_years = pd.DataFrame({"year": range(min_year, max_year + 1)})

# 各年ごとのユニークなアニメ作品数(acid)を集計
df_yearly_counts = df_ae.groupby("year")["acid"].nunique().reset_index(name="n_acid")

# 全ての年の枠に対して、集計結果をマージし、欠損値を0で埋める
df_full_analysis = pd.merge(df_years, df_yearly_counts, on="year", how="left").fillna(0)

# 作品数が0件の年(欠測年)を抽出し、リスト化
missing_years = (
    df_full_analysis[df_full_analysis["n_acid"] == 0]["year"].astype(int).tolist()
)

# 欠測している年を一覧として表示
print(f"データが完全に欠測している年(計 {len(missing_years)} 年):")
print(missing_years)
データが完全に欠測している年(計 17 年):
[1967, 1968, 1969, 1970, 1973, 1976, 1977, 1978, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989]

解説

基礎分析において「ないこと」を証明し、その範囲を特定することは非常に重要です。

.groupby() の結果をそのまま使うのではなく、あらかじめ期待される全てのレンジを用意してマージすることで、初めてデータが欠落している事実を捕捉できます。 今回のように欠測年を具体的に列挙することで、(実際に放送されていないのか、放送されていたが記録されていないのかは別として)「1980年代は一切の分析が不可能である」といった明確な制約条件を導き出すことができます。

関連セクション: 詳しくはアニメデータの基礎分析を参照してください。

応用 問題6:年代別の声優カバー率#

関連セクション: アニメデータの基礎分析

本書で扱うアニメ作品データの中には、声優情報に欠損があるものも含まれます。 どの時代の分析にこのデータが適しているのか、その限界を明らかにしましょう。

  • first_date を利用して作品を「年代(10年単位)」ごとに分類してください

  • 年代別に「総作品数」と「声優情報あり作品数」を集計してください

  • 年代別の声優データカバー率を算出してください

Hide code cell content
# 1. df_aeから作品(acid)ごとの最小の年を取得し、作品マスタを作成
df_ac_master = df_ae.groupby("acid")["year"].min().reset_index()
# 年代(decade)を計算
df_ac_master["decade"] = (df_ac_master["year"] // 10) * 10

# 2. df_ac_actから声優情報の存在するユニークなacidを抽出
df_act_exists = df_ac_act[["acid"]].drop_duplicates()
df_act_exists["has_actor"] = True

# 3. 作品マスタに声優情報の有無をマージ
df_merged = pd.merge(df_ac_master, df_act_exists, on="acid", how="left")
# 声優情報がない場合はFalseで埋める
df_merged["has_actor"] = df_merged["has_actor"].fillna(False)

# 4. 年代ごとに「総作品数」と「声優情報あり作品数」を集計
df_decade_coverage = (
    df_merged.groupby("decade")
    .agg(total=("acid", "nunique"), with_act=("has_actor", "sum"))
    .reset_index()
)

# カバー率を算出
df_decade_coverage["coverage_rate"] = (
    df_decade_coverage["with_act"] / df_decade_coverage["total"]
)

# 結果を表示
df_decade_coverage
decade total with_act coverage_rate
0 1960 2 2 1.000000
1 1970 4 4 1.000000
2 1990 454 72 0.158590
3 2000 1546 1355 0.876455
4 2010 1631 1412 0.865727

解説

データのカバー率が「一様ではない」ことを検証する問題です。

結果を見ると、1960-1980年代は作品数(total)自体が非常に少ないことがわかります。 また、比較的データサイズが安定した後の声優カバー率としては、2010年代より2000年代の方が僅かに高いことが意外な発見です。

このように事前に分析することで、「2000年以前の声優情報の扱いには注意が必要」という重要な知見を得られます。

関連セクション: 詳しくはアニメデータの基礎分析を参照してください。

応用 問題7:マルチプラットフォーム展開のペア分析#

関連セクション: ゲームデータの基礎分析

ゲーム業界において、複数のプラットフォームで同一タイトルが展開されることは珍しくありません。 どのような組み合わせが多いのか、「プラットフォームのペア」を分析してみましょう。

  • df_pkg_pf において、2つ以上のプラットフォームで発売されているタイトルを対象にしてください

  • タイトルが3機種以上に展開されている場合、全ての2機種の組み合わせを個別にカウントしてください

  • 最も頻出するプラットフォームのペアの上位10件を表示してください

Hide code cell content
# 1. タイトル(pkgname)ごとに、ユニークなプラットフォーム(pfname)のリストを抽出
df_pf_list = df_pkg_pf.groupby("pkgname")["pfname"].unique().reset_index()

# 2. 各タイトルのプラットフォームリストから、全ペア(2機種の組み合わせ)を生成
all_pairs = []
for p_array in df_pf_list["pfname"]:
    p_list = sorted(list(p_array))  # 順序を揃えてA-BとB-Aの重複を防ぐ
    if len(p_list) >= 2:
        # 組み合わせ(ペア)を生成してリストに追加
        pairs = combinations(p_list, 2)
        all_pairs.extend([" / ".join(pair) for pair in pairs])

# 3. 生成された全ペアの出現回数を集計してランキング化
df_pair_rank = pd.Series(all_pairs).value_counts().reset_index()
df_pair_rank.columns = ["pf_combination_pair", "count"]

# 上位10件を表示
df_pair_rank.head(10)
pf_combination_pair count
0 プレイステーション4 / プレイステーションVita 309
1 プレイステーション3 / プレイステーションVita 279
2 ゲームアーカイブス / プレイステーション 252
3 プレイステーション3 / プレイステーション4 230
4 Xbox360 / プレイステーション3 214
5 セガサターン / プレイステーション 116
6 WiiU / ニンテンドー3DS 112
7 プレイステーション3 / プレイステーション・ポータブル 103
8 Wii / WiiU 102
9 プレイステーション2 / プレイステーション・ポータブル 92

解説

3つ以上の多対多関係を最小単位である「ペア」に分解して分析する問題です。

単に「展開されたプラットフォームのセット」を集計すると、「PS3/PS4/Vita」という3機種セットは、2機種展開の「PS3/PS4」とは別物として扱われてしまいます。 全ペアを網羅的に抽出することで、3機種展開の中にある「PS3とPS4の強い結びつき」を正しくカウントできます。

これにより、どの機種同士が最も親和性が高いか(移植やマルチ展開のターゲットになりやすいか)という実態を把握できます。

関連セクション: 詳しくはゲームデータの基礎分析を参照してください。