上巻 第6章 解答例#
ここでは、 本書の学習内容の定着 を目的とした練習問題とその解答・解説を掲載します。 なお、問題の性質上、本書で取り上げた処理と重複することがあります。 ご了承ください。
前提#
以下のように、ライブラリのインポートと変数の定義が完了していることを前提とします。
Show 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
Show 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"
以下のファイルを読み込み済みと仮定します。
Show 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) # ゲームパッケージとプラットフォーム
Show 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() を用いて全列のユニーク値数を確認しました。
ここでは、特定の列(pfname と publisher)に絞ってユニーク値数を確認してみましょう。
df_pkg_pf から pfname 列と publisher 列を選択し、それぞれのユニーク値数を表示してください。
ヒント
DataFrameから複数列を選択するには
df[["列名1", "列名2"]]のようにリストで指定します例えば
pkgnameとdate列のユニーク値数を確認するにはdf_pkg_pf[["pkgname", "date"]].nunique()とします
Show 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_act の first_date 列と last_date 列それぞれについて、最小値と最大値を表示してください。
ヒント
日付列の最小値・最大値は
.min()と.max()で取得できます例えば
df_ae["date"].min()のように使用します
Show 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 列の記述統計量を表示してください。
ヒント
特定の条件でデータを絞り込むにはブールインデックスを使用します
例えば
pfnameが「プレイステーション」のデータを抽出するにはdf_pkg_pf[df_pkg_pf["pfname"] == "プレイステーション"]とします記述統計量は
.describe()で確認できます
Show 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件を表示してください。
ヒント
日付から年を取得するには
.dt.yearを使用します2000年以降のデータを抽出するには
df_ae[df_ae["date"].dt.year >= 2000]とします.groupby()と.nunique()で年ごとのユニーク数を集計し、.sort_values(ascending=False)で降順ソートできます
Show 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年以降に限定すると、2016、2014、2006、2007、2008…の順にアニメ作品数が多いことがわかります。
2006年から2008年にかけて、何があったのでしょうか?
興味のある方は調べてみましょう。
関連セクション: 詳しくはアニメデータの基礎分析を参照してください。
発展 問題5:放送日データの空白期間の特定#
関連セクション: アニメデータの基礎分析
第6章で触れた通り、アニメデータには特定の期間に大規模な欠損が見られます。
単純に groupby で集計すると「データが0件の年」は結果から消えてしまい、欠損の深刻さを見落とす可能性があります。
データの最小年から最大年までの全ての年を含む「枠(レンジ)」を作成してください
それに対して
df_aeの作品数を紐付けてください完全にデータが欠損している年を列挙してください。
なお、ここでは簡単のため「当該年にそもそもアニメが放送されていたかどうか(つまり0が正解かどうか)」には深入りしません。 実態は別として、 データとして アニメ作品数が0の年を抽出しましょう。
ヒント
range(min_year, max_year + 1)で全年のリストを作成できますpd.merge()のhow="left"で欠損年も保持したまま結合できます欠損値は
.fillna(0)で0に置き換えられます
Show 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年単位)」ごとに分類してください年代別に「総作品数」と「声優情報あり作品数」を集計してください
年代別の声優データカバー率を算出してください
ヒント
年代は
(year // 10) * 10で計算できます(例: 1995 → 1990)声優情報の有無は
df_ac_actにacidが存在するかでフラグ化できます.agg()で複数の集計を一度に実行できます
Show 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件を表示してください
ヒント
タイトル(
pkgname)ごとにプラットフォームのリストを.unique()で取得できますitertools.combinations()で2つの組み合わせを生成できます.value_counts()で出現回数をカウントできます
Show 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の強い結びつき」を正しくカウントできます。
これにより、どの機種同士が最も親和性が高いか(移植やマルチ展開のターゲットになりやすいか)という実態を把握できます。
関連セクション: 詳しくはゲームデータの基礎分析を参照してください。