アニメデータの前処理#
本書の再現に、前処理の再実行は不要
前処理後のデータは全てvizbook-jupyter/data/*以下に格納されています。
本書の再現のため、前処理を再実行頂く必要はありません。
(仮に再実行したとしても、同じファイルが出力されるだけですので問題はありません。)
準備#
Import#
Show code cell content
# warningsモジュールのインポート
import warnings
# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Show code cell content
# jsonモジュールのインポート
# JSON形式のデータの読み書きをサポート
import json
# osモジュールのインポート
# オペレーティングシステムとのインターフェースを提供
import os
# reモジュールのインポート
# 正規表現操作をサポート
import re
# zipfileモジュールのインポート
# ZIPアーカイブファイルの読み書きをサポート
import zipfile
# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
from pathlib import Path
# pprintモジュールのインポート
# データ構造を見やすく整形して表示するための関数
from pprint import pprint
# typingモジュールからの型ヒント関連のインポート
# 関数やクラスの引数・返り値の型を注釈するためのツール
from typing import Any, Dict, List, Optional, Union
# ijsonモジュールのインポート
# ストリームから大きなJSONオブジェクトを効率的に解析・抽出
import ijson
# numpy:数値計算ライブラリのインポート
# npという名前で参照可能
import numpy as np
# pandas:データ解析ライブラリのインポート
# pdという名前で参照可能
import pandas as pd
# tqdm_notebookのインポート
# Jupyter Notebook内でのプログレスバー表示をサポート
from tqdm import tqdm_notebook as tqdm
変数#
Show code cell content
# 入出力ディレクトリの定義
# 入力ファイル(MADB)を格納しているディレクトリのパス
DIR_INPUT = Path("../../../madb/data/json-ld")
# 外部データソース(Wikipedia)から取得したファイルを格納しているディレクトリのパス
DIR_EXTERNAL = Path("../../../data/an/external/20230224_petscan")
# 一時的にファイルを保存するディレクトリのパス
DIR_TMP = Path("../../../data/an/tmp")
# 中間ファイルを保存するディレクトリのパス
DIR_INTERIM = Path("../../../data/an/interim")
# 出力ファイルを保存するディレクトリのパス
DIR_OUTPUT = Path("../../../data/an/input")
Show code cell content
# 声優ファイル名の定義
FN_ACTORS = "actors_*.csv"
Show code cell content
# MADBの読み込み対象ファイル名のリストを定義
# - `an201`:テレビアニメレギュラー各話に関する情報を格納
# - `an207`:テレビアニメレギュラーシリーズに関する情報を格納
FNS_AN = [
"an201",
"an207",
]
Show code cell content
# Anime Collectionとして利用するカラムとその新しいカラム名のマッピングを定義
COLS_AC = {
"@id": "acid",
"name": "acname",
"originalWorkCreator": "crtname",
"actor": "actname",
"isPartOf": "asid",
}
Show code cell content
# Anime Episodeとして利用するカラムとその新しいカラム名のマッピングを定義
COLS_AE = {
"@id": "aeid",
"alternativeHeadline": "aename",
"datePublished": "date",
"episodeNumber": "aeno",
"isPartOf": "acid",
}
Show code cell content
# 声優用のデータフレームで利用するカラムとその新しいカラム名のマッピングを定義
COLS_ACT = {
"title": "actname",
"length": "wiki_size",
"gender": "gender",
}
関数#
Show code cell content
def read_json(path: Union[str, Path]) -> Dict[str, Any]:
"""
jsonファイルを辞書として読み込む
Parameters
----------
path : Union[str, Path]
読み込みたいjsonファイルのパス
Returns
-------
Dict[str, Any]
jsonデータを格納した辞書
"""
# 指定したパスのjsonファイルを読み込みモードで開く
with open(path, "r", encoding="utf-8") as f:
# json.loadを使用して、ファイル内容を辞書として読み込む
dct = json.load(f)
# 読み込んだ辞書を返す
return dct
Show code cell content
def save_json(path: Union[str, Path], dct: Dict) -> None:
"""
辞書をjson形式で保存
Parameters
----------
path : Union[str, Path]
保存先のファイルパス
dct : Dict
保存する辞書
Returns
-------
None
"""
# 指定したパスのjsonファイルを書き込みモードで開く
with open(path, "w", encoding="utf-8") as f:
# json.dumpを使用して、辞書の内容をjson形式でファイルに書き込む
# ensure_ascii=Falseで非ASCII文字もそのまま保存し、indent=4で整形して保存
json.dump(dct, f, ensure_ascii=False, indent=4)
Show code cell content
def read_json_w_filters(
path: Union[str, Path], items: List[str], filters: Dict[str, List[Any]]
) -> List[Dict[str, Any]]:
"""
itemsのうち、filtersの条件を満たすもののみを抽出して返す
Parameters
----------
path : Union[str, Path]
読み込み対象のjsonファイルのパス、文字列またはPathオブジェクト
items : List[str]
読み込む項目名のリスト
filters : Dict[str, List[Any]]
抽出条件を指定する辞書、キーはフィルタリング対象の項目名、値は条件となる値のリスト
Returns
-------
List[Dict[str, Any]]
フィルタリングされた項目の辞書を要素とするリスト
"""
# 出力結果を格納するための空のリストを初期化
out = []
# 指定したパスからファイルを読み込みモードで開く
with open(path, "r", encoding="utf-8") as f:
# ijsonを使用して、特定の項目を逐次読み込む
parse = ijson.items(f, items)
# parseを順に処理し、各項目をitemとして取得
for item in parse:
# filtersの条件をすべて満足するもの以外はbreak
for k, v in filters.items():
# フィルタリング対象の項目名がitemのキーに含まれていない場合、break
if k not in item.keys():
break
# 項目の値がフィルタリング条件に含まれていない場合、break
if item[k] not in v:
break
else:
# 上記のforループでbreakされなかった場合(全ての条件を満たす場合)、outに追加
out.append(item)
# 処理結果を返す
return out
Show code cell content
def format_cols(df: pd.DataFrame, cols_rename: Dict[str, str]) -> pd.DataFrame:
"""
指定されたカラムのみをデータフレームから抽出し、カラム名をリネームする関数
Parameters
----------
df : pd.DataFrame
入力データフレーム
cols_rename : Dict[str, str]
リネームしたいカラム名のマッピング(元のカラム名: 新しいカラム名)
Returns
-------
pd.DataFrame
カラムが抽出・リネームされたデータフレーム
"""
# 指定されたカラムのみを抽出し、リネーム
df = df[cols_rename.keys()].rename(columns=cols_rename)
return df
Show code cell content
def format_name(name: Optional[str]) -> Optional[str]:
"""
nameから名称情報を抽出する関数
ccname, crtname, ocrtnameの値の生成に利用
Parameters
----------
name : Optional[str]
名称情報を含むデータ
Returns
-------
Optional[str]
抽出された名称情報、nameがNoneまたは適切な形式でない場合はNoneを返す
Raises
------
Exception:
nameから適切な名称情報を抽出できなかった場合
"""
# nameがNoneまたは辞書の場合
if name is np.nan or isinstance(name, dict):
return None
# nameが文字列の場合
if isinstance(name, str):
return name
# nameがリストの場合
if isinstance(name, list):
for item in name:
if isinstance(item, str):
return item
# 上記の条件に合致しない場合、例外を発生させる
raise Exception(f"No name in {name}!")
Show code cell content
def get_id_from_uri(uri: Optional[str]) -> Optional[str]:
"""
URIから末尾のIDを取得する関数
Parameters
----------
uri : Optional[str]
解析対象のURI、Noneの場合も考慮
Returns
-------
Optional[str]
URIから取得したID、URIがNoneまたはNaNの場合はNoneを返す
"""
# uriがNaNの場合、Noneを返す
if uri is np.nan:
return None
# uriからIDを抽出して返す
else:
return uri.split("/")[-1]
Show code cell content
def preprocess_df_ac(df: pd.DataFrame) -> pd.DataFrame:
"""
`df_ac` の前処理を行う関数
Parameters
----------
df : pd.DataFrame
前処理前の`df_ac`
Returns
-------
pd.DataFrame
前処理が完了した`df_ac`
"""
# 必要なカラムの抽出とカラム名のリネーム
df = format_cols(df, COLS_AC)
# 声優名の整形
df["acname"] = df["acname"].apply(format_name)
# 声優IDの抽出
df["acid"] = df["acid"].apply(get_id_from_uri)
# アニメシリーズIDの抽出
df["asid"] = df["asid"].apply(get_id_from_uri)
# 作品名のテキストから原作者名を抽出
df["crtname"] = df["crtname"].apply(get_crtnames_from_text)
return df
Show code cell content
def preprocess_df_ae(df: pd.DataFrame) -> pd.DataFrame:
"""
`df_ae` の前処理を行う関数
Parameters
----------
df : pd.DataFrame
前処理前の`df_ae`
Returns
-------
pd.DataFrame
前処理が完了した`df_ae`
"""
# 必要なカラムの抽出とカラム名のリネーム
df = format_cols(df, COLS_AE)
# 声優IDの抽出
df["acid"] = df["acid"].apply(get_id_from_uri)
# アニメ各話IDの抽出
df["aeid"] = df["aeid"].apply(get_id_from_uri)
# アニメ各話名の整形
df["aename"] = df["aename"].apply(format_name)
return df
Show code cell content
def preprocess_df_act(df: pd.DataFrame) -> pd.DataFrame:
"""
`df_act` の前処理を行う関数
Parameters
----------
df : pd.DataFrame
前処理前の`df_act`
Returns
-------
pd.DataFrame
前処理が完了した`df_act`
"""
# `namespace` がNaNのレコードのみを残す
df = df[df["namespace"].isna()].reset_index(drop=True)
# 必要なカラムの抽出とカラム名のリネーム
df = format_cols(df, COLS_ACT)
# キャラクター名から括弧とその中身を削除
df["actname"] = df["actname"].replace("_\(.*\)", "", regex=True)
# `wiki_size` に基づいてソート
df = df.sort_values("wiki_size", ascending=False, ignore_index=True)
return df
Show code cell content
def get_actname_from_text(text: Optional[Any], names_all: List[str]) -> Optional[str]:
"""
与えられたテキストから`names_all`に一致する名前を抽出する関数
Parameters
----------
text : Optional[Any]
名前を抽出する対象のテキスト
names_all : List[str]
全名前のリスト
Returns
-------
Optional[str]
一致する名前。一致しない場合はNone
"""
# '【役名】名前' -> '名前'
text = re.sub("【.*】", "", text)
# '[役名]名前' -> '名前'
text = re.sub("\[.*\]", "", text)
# 〈.*〉で区切られている場合に対応
text = re.sub("〈.*〉", "", text)
# <.*>で区切られている場合に対応
text = re.sub("<.*>", "", text)
# 空白文字を削除
text = re.sub("\s*", "", text)
name_matches = [x for x in names_all if x in text]
if name_matches:
# 一致した名前の中から最も長いものを返す
return sorted(name_matches, key=len, reverse=True)[0]
else:
return None
Show code cell content
def get_actnames_from_text(
text: Optional[str], names_all: List[str]
) -> Optional[List[str]]:
"""
与えられたテキストから`names_all`に一致する名前のリストを抽出する関数
Parameters
----------
text : Optional[str]
名前を抽出する対象のテキスト
names_all : List[str]
全ての名前のリスト
Returns
-------
Optional[List[str]]
一致する名前のリスト、一致しない場合はNone
"""
# textがNaNやNoneの場合はNoneを返す
if pd.isna(text) or text is None:
return None
# 名前を抽出
extracted_names = [
get_actname_from_text(name, names_all) for name in text.split("/")
]
# Noneを除外
extracted_names = [name for name in extracted_names if name is not None]
return list(set(extracted_names)) if extracted_names else None
Show code cell content
def get_crtname_from_text(text: str) -> str:
"""
与えられたテキストから原作者名を抽出する関数
Parameters
----------
text : str
原作者名を抽出する対象のテキスト
Returns
-------
str
抽出された原作者名
"""
# 全角の角括弧とその中身を削除
text = re.sub("[.*]", "", text)
# 空白文字を削除
text = re.sub("\s*", "", text)
return text
Show code cell content
def get_crtnames_from_text(text: str) -> Optional[List[str]]:
"""
文字列から原作者名のリストを抽出する。
Parameters:
- text (str): 原作者名が"/"で区切られた文字列。
Returns:
- Optional[List[str]]: 抽出された原作者名のリスト。入力がNaNの場合はNoneを返す。
"""
# 入力がNaNであるかの確認
if text is np.nan:
return None
# "/"で区切られた原作者名を分割する
crtnames = text.split("/")
# 各原作者名を整形する
crtnames = [get_crtname_from_text(c) for c in crtnames]
return crtnames
Show code cell content
def read_csvs(pathes: List) -> pd.DataFrame:
"""
複数のCSVファイルを順番に読み込み、それらを結合する関数
Parameters
----------
pathes : List
読み込みたいCSVファイルのパスのリスト
Returns
-------
pd.DataFrame
結合されたデータフレーム
"""
# 空のデータフレームを初期化
df_all = pd.DataFrame()
# 各CSVファイルのパスについて処理を行う
for p in pathes:
# CSVファイルを読み込む
df = pd.read_csv(p)
# 読み込んだデータフレームをdf_allに結合する
df_all = pd.concat([df_all, df], ignore_index=True)
return df_all
Show code cell content
def cast_str_to_list(text: Optional[str]) -> List[str]:
"""
文字列形式で表現されたリストを、実際のリストに変換する関数
Parameters
----------
text : Optional[str]
リストとして文字列で表現されたデータ(例: "[1, 2, 3]")
Returns
-------
List[str]
文字列から変換されたリスト:元の文字列がNaNの場合は空のリストを返す
Example
-------
>>> cast_str_to_list("[a, b, c]")
['a', 'b', 'c']
"""
# NaNの場合は空のリストを返す
if text is np.nan:
return []
# 不要な文字を取り除いて、カンマで分割
# この操作で文字列形式のリストを実際のリストに変換する
return (
text.replace("[", "")
.replace("]", "")
.replace("'", "")
.replace(" ", "")
.split(",")
)
出力先の生成#
Show code cell content
# DIR_TMPという名前のディレクトリを作成する
# すでに存在する場合は何もしない
DIR_TMP.mkdir(exist_ok=True, parents=True)
# DIR_INTERIMという名前のディレクトリを作成する
# すでに存在する場合は何もしない
DIR_INTERIM.mkdir(exist_ok=True, parents=True)
# DIR_OUTPUTという名前のディレクトリを作成する
# すでに存在する場合は何もしない
DIR_OUTPUT.mkdir(exist_ok=True, parents=True)
DIR_TMPへの一時的な出力#
zipファイルの解凍#
Show code cell content
# DIR_INPUTディレクトリ内で`_an`を含むファイルのパスをすべて検索し、リストとして取得
ps_an = sorted(list(DIR_INPUT.glob("*_an-*")))
Show code cell content
# `ps_an`リストに含まれる各.zipファイルに対して処理を実行
# tqdmを使用することで、進行状況のバーが表示される
for p_from in tqdm(ps_an):
# 出力先のパスを設定する
# 元のファイルパスから、DIR_INPUTをDIR_TMPに変更し、ファイル拡張子の.zipを削除
p_to = DIR_TMP / p_from.parts[-1].replace(".zip", "")
# zipfileを使用して、zipファイルを開く
with zipfile.ZipFile(p_from) as z:
# zipファイル内のすべてのファイル・ディレクトリをp_toのパスに展開
z.extractall(p_to)
入力ファイルのサイズ圧縮#
対象#
Show code cell content
# MADBの各ファイル名をキーとして、該当するファイルのパスをリストとして取得する
# これを辞書型変数`ps_an`に格納する
# 例: {'an201': ['path1', 'path2', ...], 'an207': ['path3', 'path4', ...],}
ps_an = {an: sorted(list(DIR_TMP.glob(f"*{an}*/*"))) for an in FNS_AN}
Show code cell content
# 内容を確認
pprint(ps_an)
{'an201': [PosixPath('../../../data/an/tmp/metadata_an-item_an201_json/metadata_an-item_an201_json\\metadata_an-item_an201_00001.json'),
PosixPath('../../../data/an/tmp/metadata_an-item_an201_json/metadata_an-item_an201_json\\metadata_an-item_an201_00002.json')],
'an207': [PosixPath('../../../data/an/tmp/metadata_an-col_an207_json/metadata_an-col_an207_json\\metadata_an-col_an207_00001.json')]}
an207#
Show code cell content
# `an207`に関連するファイルパスを抽出する
ps_an207 = ps_an["an207"]
Show code cell content
# ps_an207に含まれる全てのファイルパスに対して処理を繰り返す
for i, p_an207 in enumerate(ps_an207):
# フィルタリング対象となるジャンルを指定
# ここでは"テレビレギュラーアニメシリーズ"のみを対象とする
filters = {
"genre": ["テレビレギュラーアニメシリーズ"],
}
# 指定したフィルターを利用して、JSONファイルからデータを読み込む
ac = read_json_w_filters(p_an207, "@graph.item", filters)
# 読み込んだデータをデータフレームに変換
df_ac = pd.DataFrame(ac)
# データフレームの前処理を実行
df_ac = preprocess_df_ac(df_ac)
# 前処理後のデータをCSVファイルとして一時保存
# ファイル名は連番を含めた形式にしている
df_ac.to_csv(DIR_TMP / f"an207_{i+1:05}.csv", index=False)
an201#
Show code cell content
# `an201`に関連するファイルパスを抽出する
ps_an201 = ps_an["an201"]
Show code cell content
# ps_an201に含まれる全てのファイルパスに対して処理を繰り返す
for i, p_an201 in enumerate(ps_an201):
# フィルタリング対象となるジャンルを指定
# ここでは"テレビレギュラー"のみを対象とする
filters = {"genre": ["テレビレギュラー"]}
# 指定したフィルターを利用して、JSONファイルからデータを読み込む
ae = read_json_w_filters(p_an201, "@graph.item", filters)
# 読み込んだデータをデータフレームに変換
df_ae = pd.DataFrame(ae)
# データフレームの前処理を実行
df_ae = preprocess_df_ae(df_ae)
# 前処理後のデータをCSVファイルとして一時保存
# ファイル名は連番を含めた形式にしている
df_ae.to_csv(DIR_TMP / f"an201_{i+1:05}.csv", index=False)
Wikipedia声優データの前処理#
Show code cell content
# 声優データが格納されている、下記のパターンに合致するファイルのパス一覧を取得する
ps_actors = sorted(list(DIR_EXTERNAL.glob(FN_ACTORS)))
Show code cell content
# 空のデータフレームを初期化
df_act = pd.DataFrame()
# 各声優ファイルを順番に処理
for p_act in ps_actors:
# ファイル名から性別情報を抽出
gnd = p_act.parts[-1].split("_")[-1].replace(".csv", "")
# CSVファイルを読み込み
df = pd.read_csv(p_act)
# 新たに性別カラムを作成し、性別情報を追加
df["gender"] = gnd
# 既存のデータフレームに新たに読み込んだデータを結合
df_act = pd.concat([df_act, df], ignore_index=True)
# データフレームの列を整理
df_act = format_cols(df_act, COLS_ACT)
Show code cell content
# 作成したパスにデータフレームをCSVとして保存
df_act.to_csv(DIR_TMP / "act.csv", index=False)
DIR_INTERIMへの中間出力#
ac.csv#
Show code cell content
# `DIR_TMP`ディレクトリ内の`an207_`で始まるCSVファイルのパスをすべて取得する
ps_ac = sorted(list(DIR_TMP.glob("an207_*.csv")))
# 取得したCSVファイルを読み込み、一つのデータフレームに結合する
df_ac = read_csvs(ps_ac)
Show code cell content
# データフレームからacidとacnameの列のみを取得する
df_ac = df_ac[["acid", "acname", "asid"]]
Show code cell content
# 所定のディレクトリにdf_acをCSVファイルとして保存
df_ac.to_csv(DIR_INTERIM / "ac.csv", index=False)
ae.csv#
Show code cell content
# `DIR_TMP`ディレクトリ内の`an201_`で始まるCSVファイルのパスをすべて取得する
ps_ae = sorted(list(DIR_TMP.glob("an201_*.csv")))
# 取得したCSVファイルを読み込み、一つのデータフレームに結合する
df_ae = read_csvs(ps_ae)
# dateとacidを基準にデータフレームを昇順にソートする
df_ae = df_ae.sort_values(["date", "acid"], ignore_index=True)
Show code cell content
# head()メソッドを利用し、先頭5行を表示する
df_ae.head()
| aeid | aename | date | aeno | acid | |
|---|---|---|---|---|---|
| 0 | M19760 | アトム誕生の巻* | 1963-01-01 | 第1話 | C7163 |
| 1 | M19761 | フランケンの巻* | 1963-01-08 | 第2話 | C7163 |
| 2 | M19762 | 火星探険の巻* | 1963-01-15 | 第3話 | C7163 |
| 3 | M19763 | ゲルニカの巻* | 1963-01-22 | 第4話 | C7163 |
| 4 | M19764 | スフィンクスの巻* | 1963-01-29 | 第5話 | C7163 |
Show code cell content
# 所定のディレクトリにdf_aeをCSVファイルとして保存
df_ae.to_csv(DIR_INTERIM / "ae.csv", index=False)
act.csv#
Show code cell content
# `DIR_TMP`ディレクトリ内の`act.csv`という名称のCSVファイルのパスをすべて取得する
ps_act = sorted(list(DIR_TMP.glob("act.csv")))
# 取得したCSVファイルを読み込み、一つのデータフレームに結合する
df_act = read_csvs(ps_act)
Show code cell content
# 現在のデータフレームの列のリストを取得
cols = df_act.columns.tolist()
# genderとactnameで並び替え
df_act = df_act.sort_values(["gender", "actname"], ignore_index=True)
# 新しい列`actid`を追加し、インデックス番号をそのまま値として格納
df_act["actid"] = df_act.index
# `actid`の値を「ACT」に続く5桁の数字の形式に変換
df_act["actid"] = df_act["actid"].apply(lambda x: f"ACT{x:05}")
# 列の順番を`actid`を先頭にして変更
df_act = df_act[["actid"] + cols]
Show code cell content
# head()メソッドを利用し、先頭5行を表示する
df_act.head()
| actid | actname | wiki_size | gender | |
|---|---|---|---|---|
| 0 | ACT00000 | AIRI_(声優) | 2597 | female |
| 1 | ACT00001 | AKIKO_(声優) | 4359 | female |
| 2 | ACT00002 | AYA_(声優) | 4471 | female |
| 3 | ACT00003 | Ashir | 2057 | female |
| 4 | ACT00004 | Ayami_(アニソン歌手) | 6910 | female |
Show code cell content
# 所定のディレクトリにdf_actをCSVファイルとして保存
df_act.to_csv(DIR_INTERIM / "act.csv", index=False)
crt.csv#
Show code cell content
# `DIR_TMP`ディレクトリ内の`an207_`で始まるCSVファイルのパスをすべて取得する
ps_ac = sorted(list(DIR_TMP.glob("an207_*.csv")))
# 取得したCSVファイルを読み込み、一つのデータフレームに結合する
df_ac = read_csvs(ps_ac)
Show code cell content
# `crtnames`という名前の空の集合を初期化
crtnames = set()
# `df_ac`の各行に対して処理を実行
for r in df_ac.to_dict("records"):
# `crtname`がNaNの場合は次の行へ
if r["crtname"] is np.nan:
continue
# `crtname`をリストに変換し、一時的な集合を作成
crtname = set(cast_str_to_list(r["crtname"]))
# `crtnames`集合に`crtname`集合の要素を追加
crtnames.update(crtname)
# `crtnames`集合をリストに変換し、ソート
crtnames = sorted(list(crtnames))
Show code cell content
# マンガ作者(原作者)名の数だけ固有のIDを生成
crtids = [f"ACRT{i:05}" for i in range(len(crtnames))]
# 生成したIDとマンガ作者(原作者)名を使用してDataFrameを作成
df_crt = pd.DataFrame({"crtid": crtids, "crtname": crtnames})
Show code cell content
# head()メソッドを利用して、先頭5行の内容を確認
df_crt.head()
| crtid | crtname | |
|---|---|---|
| 0 | ACRT00000 | 29 |
| 1 | ACRT00001 | 5pb./Nitroplus |
| 2 | ACRT00002 | 6pack |
| 3 | ACRT00003 | ACQUIRE |
| 4 | ACRT00004 | AIC |
Show code cell content
# 所定のディレクトリにdf_crtをCSVファイルとして保存
df_crt.to_csv(DIR_INTERIM / "crt.csv", index=False)
ac_act.csv#
Show code cell content
# `DIR_TMP`ディレクトリ内の`an207_`で始まるCSVファイルのパスをすべて取得する
ps_ac = sorted(list(DIR_TMP.glob("an207_*.csv")))
# 取得したCSVファイルを読み込み、一つのデータフレームに結合する
df_ac = read_csvs(ps_ac)
# `DIR_INTERIM`ディレクトリ内の`act.csv`ファイルのパスを取得する
ps_act = sorted(list(DIR_INTERIM.glob("act.csv")))
# 取得したCSVファイルを読み込む
df_act = read_csvs(ps_act)
Show code cell content
# `df_act`から一意の声優名を取得する
actnames_all = sorted(df_act["actname"].unique())
# `df_act`を用いて、声優名をキーとし、対応する`actid`を値とする辞書を作成する
actname2atcid = df_act.groupby("actname")["actid"].first().to_dict()
Show code cell content
# `df_ac`から`acid`と`actname`のみを抽出して新しいデータフレームを作成
df_tmp = df_ac[["acid", "actname"]]
# `get_actnames_from_text`関数を用いて、`actname`列内の声優名を整形
df_tmp["actname"] = df_tmp["actname"].apply(
lambda x: get_actnames_from_text(x, actnames_all)
)
# NaNを含む行を除去し、インデックスをリセット
df_tmp = df_tmp[~df_tmp["actname"].isna()].reset_index(drop=True)
Show code cell content
# 各アニメ作品(ac)に対応する声優(act)の情報を集約するためのリストを初期化
ac_act = []
# `df_tmp`の各行に対して処理を実行
for r in df_tmp.to_dict("records"):
# アニメ作品のIDを取得
acid = r["acid"]
# 当該アニメ作品に対応する声優名のリストを取得
actnames = sorted(r["actname"])
# 各声優名に対して、声優IDを取得し、結果をリストに追加
for actname in actnames:
actid = actname2atcid[actname]
ac_act.append([acid, actid])
# 得られた結果をデータフレームに変換
df_ac_act = pd.DataFrame(columns=["acid", "actid"], data=ac_act)
Show code cell content
# head()メソッドを利用し、先頭5行を表示
df_ac_act.head()
| acid | actid | |
|---|---|---|
| 0 | C7158 | ACT06218 |
| 1 | C7158 | ACT01691 |
| 2 | C7158 | ACT02696 |
| 3 | C7162 | ACT00975 |
| 4 | C7162 | ACT06522 |
Show code cell content
# 所定のディレクトリにdf_ac_actをCSVファイルとして保存
df_ac_act.to_csv(DIR_INTERIM / "ac_act.csv", index=False)
ac_crt.csv#
Show code cell content
# `DIR_TMP`ディレクトリ内の`an207_`で始まるCSVファイルのパスをすべて取得
ps_ac = sorted(list(DIR_TMP.glob("an207_*.csv")))
# `DIR_INTERIM`ディレクトリ内の`crt.csv`という名前のCSVファイルのパスを取得
ps_crt = sorted(list(DIR_INTERIM.glob("crt.csv")))
# 取得したアニメ作品に関するCSVファイルを読み込み、一つのデータフレームに結合
df_ac = read_csvs(ps_ac)
# 取得した原作者に関するCSVファイルを読み込み、一つのデータフレームに結合
df_crt = read_csvs(ps_crt)
Show code cell content
# `df_crt`を用いて、原作者名をキーとし、対応する`crtid`を値とする辞書を作成する
crtname2crtid = df_crt.groupby("crtname")["crtid"].first().to_dict()
Show code cell content
# アニメ作品と原作者の関連をリストに格納するための空リストを作成
ac_crt = []
# アニメ作品のデータフレームをレコード毎に処理
for r in df_ac.to_dict("records"):
# アニメ作品のIDを取得
acid = r["acid"]
# アニメ作品に関連する原作者名をセットに変換
crtnames = sorted(set(cast_str_to_list(r["crtname"])))
# 各原作者名に対して処理
for crtname in crtnames:
# 原作者名から原作者IDを取得
crtid = crtname2crtid[crtname]
# アニメ作品IDと原作者IDのペアをリストに追加
ac_crt.append([acid, crtid])
# アニメ作品と原作者の関連を示すデータフレームを作成
df_ac_crt = pd.DataFrame(columns=["acid", "crtid"], data=ac_crt)
Show code cell content
# head()メソッドを利用し、先頭5行を表示
df_ac_crt.head()
| acid | crtid | |
|---|---|---|
| 0 | C7158 | ACRT00664 |
| 1 | C7158 | ACRT00837 |
| 2 | C7158 | ACRT00937 |
| 3 | C7160 | ACRT00799 |
| 4 | C7162 | ACRT00778 |
Show code cell content
# 所定のディレクトリにdf_ac_crtをCSVファイルとして保存
df_ac_crt.to_csv(DIR_INTERIM / "ac_crt.csv", index=False)
DIR_OUTPUTへの最終出力#
Show code cell content
# ファイルから各データフレームを読み込む
# アニメ各話に関する情報を読み込む
df_ae = pd.read_csv(DIR_INTERIM / "ae.csv")
# アニメ原作者に関する情報を読み込む
df_crt = pd.read_csv(DIR_INTERIM / "crt.csv")
# アニメ作品とアニメ原作者の対応関係に関する情報を読み込む
df_ac_crt = pd.read_csv(DIR_INTERIM / "ac_crt.csv")
# アニメ作品に関する情報を読み込む
df_ac = pd.read_csv(DIR_INTERIM / "ac.csv")
# 声優に関する情報を読み込む
df_act = pd.read_csv(DIR_INTERIM / "act.csv")
# アニメ作品と声優の対応関係に関する情報を読み込む
df_ac_act = pd.read_csv(DIR_INTERIM / "ac_act.csv")
an_ae.csv#
Show code cell content
# 各データフレームを統合する
# `df_ae`と`df_ac`を`acid`をキーにして統合
df_an_ae = pd.merge(df_ae, df_ac, on="acid", how="left").reset_index(drop=True)
# 必須列である`date`あるいは`acid`が欠損している行をdf_an_ae_droppedとして保持
# (後の処理でデバッグ用途で利用)
df_an_ae_dropped = df_an_ae.loc[
df_an_ae["date"].isna() | df_an_ae["acid"].isna()
].reset_index(drop=True)
# 必須列である`date`あるいは`acid`列が欠損しているレコードを削除
df_an_ae = df_an_ae.dropna(subset=["date", "acid"]).reset_index(drop=True)
Show code cell content
# head()メソッドで先頭5行を確認
df_an_ae.head()
| aeid | aename | date | aeno | acid | acname | asid | |
|---|---|---|---|---|---|---|---|
| 0 | M19760 | アトム誕生の巻* | 1963-01-01 | 第1話 | C7163 | 鉄腕アトム | C979 |
| 1 | M19761 | フランケンの巻* | 1963-01-08 | 第2話 | C7163 | 鉄腕アトム | C979 |
| 2 | M19762 | 火星探険の巻* | 1963-01-15 | 第3話 | C7163 | 鉄腕アトム | C979 |
| 3 | M19763 | ゲルニカの巻* | 1963-01-22 | 第4話 | C7163 | 鉄腕アトム | C979 |
| 4 | M19764 | スフィンクスの巻* | 1963-01-29 | 第5話 | C7163 | 鉄腕アトム | C979 |
Show code cell content
# `aeid`列の値に重複がないことをアサーションで確認
assert df_an_ae.duplicated(subset=["aeid"]).sum() == 0
Show code cell content
# データフレーム`df_an_ae`をCSVファイルとして保存
# 保存先のパスは、`DIR_OUTPUT`ディレクトリ内の`an_ae.csv`
df_an_ae.to_csv(DIR_OUTPUT / "an_ae.csv", index=False)
Show code cell content
# データフレーム`df_an_ae`をCSVファイルとして保存
# 保存先のパスは、`DIR_INTERIM`ディレクトリ内の`ae_dropped.csv`
df_an_ae_dropped.to_csv(DIR_INTERIM / "ae_dropped.csv", index=False)
Show code cell content
# `acid`ごとに`aeid`のユニークな数(話数)を集計
df_ac_nae = df_an_ae.groupby("acid")["aeid"].nunique().reset_index(name="n_ae")
# `acid`ごとに最初の放送日を取得
df_ac_fdate = df_an_ae.groupby("acid")["date"].min().reset_index(name="first_date")
# `acid`ごとに最後の放送日を取得
df_ac_ldate = df_an_ae.groupby("acid")["date"].max().reset_index(name="last_date")
# 上記で作成したデータフレームを`df_ac`にマージして、新しいデータフレームを作成
df_ac_merge = pd.merge(df_ac, df_ac_nae, on="acid", how="right").reset_index(drop=True)
df_ac_merge = pd.merge(df_ac_merge, df_ac_fdate, on="acid", how="left").reset_index(
drop=True
)
df_ac_merge = pd.merge(df_ac_merge, df_ac_ldate, on="acid", how="left").reset_index(
drop=True
)
Show code cell content
# データフレーム`df_ac_merge`をCSVファイルとして保存
# 保存先のパスは、`DIR_INTERIM`ディレクトリ内の`ac_merge.csv`
df_ac_merge.to_csv(DIR_INTERIM / "ac_merge.csv", index=False)
an_ac_crt.csv#
Show code cell content
# `df_ac_merge`と`df_ac_crt`を`acid`をキーにして統合
df_an_ac_crt = pd.merge(df_ac_merge, df_ac_crt, on="acid", how="left").reset_index(
drop=True
)
# 結果を`df_crt`と`crtid`をキーにしてさらに統合
df_an_ac_crt = pd.merge(df_an_ac_crt, df_crt, on="crtid", how="left").reset_index(
drop=True
)
# `crtid`が欠損しているレコードを削除
df_an_ac_crt = df_an_ac_crt.dropna(subset=["crtid"]).reset_index(drop=True)
Show code cell content
# head()メソッドで先頭5行を表示
df_an_ac_crt.head()
| acid | acname | asid | n_ae | first_date | last_date | crtid | crtname | |
|---|---|---|---|---|---|---|---|---|
| 0 | C10010 | グラビテーション | C2336 | 13 | 2000-10-04 | 2001-01-10 | ACRT00944 | 村上真紀 |
| 1 | C12657 | ヒピラくん 原作/大友克洋 | C3943 | 10 | 2009-12-21 | 2009-12-24 | ACRT00733 | 大友克洋 |
| 2 | C12663 | カウボーイ ビバップ[WOWOW放送版] | C2111 | 26 | 1998-10-24 | 1999-04-24 | ACRT01173 | 矢立肇 |
| 3 | C12681 | ドラえもん[新] | NaN | 224 | 1999-12-03 | 2005-03-18 | ACRT01283 | 藤子・F・不二雄 |
| 4 | C13191 | HUNTER × HUNTER[新] | C2136 | 149 | 2011-10-02 | 2014-09-24 | ACRT00647 | 冨樫義博 |
Show code cell content
# `df_an_ac_crt`内の`acid`と`crtid`の組み合わせが重複していないことを確認
assert df_an_ac_crt.duplicated(subset=["acid", "crtid"]).sum() == 0
Show code cell content
# データフレーム`df_an_ac_crt`をCSVファイルとして保存
# 保存先のパスは、`DIR_OUTPUT`ディレクトリ内の`an_acc_crt.csv`
df_an_ac_crt.to_csv(DIR_OUTPUT / "an_ac_crt.csv", index=False)
an_ac_act.csv#
Show code cell content
# `df_ac_merge`と`df_ac_act`を`acid`をキーにして統合し、アニメ作品と声優の関係を表すデータフレームを作成
df_an_ac_act = pd.merge(df_ac_merge, df_ac_act, on="acid", how="left").reset_index(
drop=True
)
# 結果を`df_act`と`actid`をキーにしてさらに統合し、声優の詳細情報を追加
df_an_ac_act = pd.merge(df_an_ac_act, df_act, on="actid", how="left").reset_index(
drop=True
)
# `actid`が欠損しているレコードを削除
df_an_ac_act = df_an_ac_act.dropna(subset=["actid"]).reset_index(drop=True)
Show code cell content
# head()メソッドで先頭5行を確認
df_an_ac_act.head()
| acid | acname | asid | n_ae | first_date | last_date | actid | actname | wiki_size | gender | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | C10001 | ギャラクシー エンジェル | C2483 | 24 | 2001-04-08 | 2001-09-30 | ACT00102 | かないみか | 116003.0 | female |
| 1 | C10001 | ギャラクシー エンジェル | C2483 | 24 | 2001-04-08 | 2001-09-30 | ACT05700 | 保村真 | 45464.0 | male |
| 2 | C10001 | ギャラクシー エンジェル | C2483 | 24 | 2001-04-08 | 2001-09-30 | ACT06001 | 吉野裕行 | 149454.0 | male |
| 3 | C10001 | ギャラクシー エンジェル | C2483 | 24 | 2001-04-08 | 2001-09-30 | ACT01887 | 山口眞弓 | 19635.0 | female |
| 4 | C10001 | ギャラクシー エンジェル | C2483 | 24 | 2001-04-08 | 2001-09-30 | ACT02359 | 新谷良子 | 73259.0 | female |
Show code cell content
# `df_an_ac_act`内の`acid`と`actid`の組み合わせが重複していないことを確認
assert df_an_ac_act.duplicated(subset=["acid", "actid"]).sum() == 0
Show code cell content
# データフレーム`df_an_ac_act`をCSVファイルとして保存
# 保存先のパスは、`DIR_OUTPUT`ディレクトリ内の`an_ac_act.csv`
df_an_ac_act.to_csv(DIR_OUTPUT / "an_ac_act.csv", index=False)