ChatGPTに教えてもらいながら映画ジャンルのトレンドを可視化してみた

## はじめに
普段はハードウェア(メカ系・電気系)の技術職をしており、プログラミングをすることはありません。
キーボードよりも工具を触っている時間が多い日もあります。
ただ、プログラミングへの興味はあったのでChatGPT(4o)に色々相談してみたところ「映画データを使ったジャンル分析」を提案されました。

この投稿では、[IMDb](https://www.imdb.com/)のデータを使って、
- 映画ジャンルの年代別トレンドを分析・可視化

を行った流れを記載します。

ちなみに映画がテーマになったのは私が映画好きとChatGPTに教えたからです。

## 使用データ
- IMDb公式の公開データ
https://developer.imdb.com/non-commercial-datasets/

## 環境
* python 3.12.8
* Jupyter Notebook

## 分析の流れ
### データの取得
IMDbの [公式データ](https://datasets.imdbws.com/) から `title.basics.tsv.gz` や `title.ratings.tsv.gz` をダウンロードしました。
* title.basics.tsv.gz
    * 公開年
        * startYear (YYYY) – represents the release year of a title. In the case of TV Series, it is the series start year
    * ジャンル
        * genres (string array) – includes up to three genres associated with the title

* title.ratings.tsv.gz
    * 評価値の平均値
        * averageRating – weighted average of all the individual user ratings
    * 評価の票数
        * numVotes - number of votes the title has received


### 必要なライブラリのインポート
```python
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
```

日本語フォントの設定。
最初は設定していなかったのですが、のちのちグラフ化した際に表ラベル(日本語)が□□になってしまう問題が発生しました。
```python
# 日本語フォントの設定
plt.rcParams['font.family'] = 'Meiryo'  # WindowsならMeiryo、Yu Gothicなど

# マイナス記号が文字化けしないように
matplotlib.rcParams['axes.unicode_minus'] = False
```

### データの読み込みと分類
pandasを使ってデータを読み込み、映画のみに絞り込みました。

```python
file_path = '../data/title.basics.tsv.gz'  # Notebookからの相対パス
df = pd.read_csv(file_path, sep='\t', na_values='\\N', low_memory=False)

# 映画だけに絞る
df = df[df['titleType'] == 'movie'].copy()

# 年代(10年ごと)の設定
df['startYear'] = pd.to_numeric(df['startYear'], errors='coerce')
df = df.dropna(subset=['startYear'])
df['decade'] = (df['startYear'] // 10) * 10
```
ジャンル分け。
```python
# 複数ジャンルがカンマ区切りで入っている
df['genres'] = df['genres'].fillna('')
df_exploded = df.assign(genres=df['genres'].str.split(',')).explode('genres')
df_exploded = df_exploded[df_exploded['genres'] != '']
```
### 可視化
```python
genre_decade = df_exploded.groupby(['decade', 'genres']).size().unstack().fillna(0)
plt.figure(figsize=(12, 8))
sns.heatmap(genre_decade.T, cmap='YlGnBu', linewidths=0.5)
plt.title("IMDb映画ジャンルの年代別傾向")
plt.xlabel("年代")
plt.ylabel("ジャンル")
plt.tight_layout()
```
![genre_trends_by_decade.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692857/7c126bef-421b-467e-ad0e-ea4a27b5ca28.png)

いったん可視化できました。

### さらなる絞り込み
映画というタイプのみの絞りこみでは本数が50,000越えと非常に多くなってしまいました。
一定数のレビューをもらっている、かつある程度の評価をされているという条件で映画を絞り込みます。
```python
ratings = pd.read_csv('../data/title.ratings.tsv.gz', sep='\t')
df_filtered = df_exploded.merge(ratings, on='tconst', how='inner')
df_filtered = df_filtered[
    (df_filtered['averageRating'] >= 7.0) &
    (df_filtered['numVotes'] >= 1000)
]
```
今振り返ってみると、ChatGPTの提案通りのレビュー数(=1000)で絞り込みましたが、人気作に絞るのであればもう少し数値をあげるべきです。
[アベンジャーズ：エンドゲーム](https://www.imdb.com/title/tt4154796/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_avengers%2520end)　レビュー数：1.4M(=1,400,000)、評価：8.4　
(注)2025年5月4日時点
この部分は要調整。評価(=7.0)についても同様。

もう一度可視化。
```python
genre_decade_highrated = df_filtered.groupby(['decade', 'genres']).size().unstack().fillna(0)
plt.figure(figsize=(12, 8))
sns.heatmap(genre_decade_highrated.T, cmap='YlGnBu', linewidths=0.5)
plt.title("IMDb高評価映画に限定したジャンルの年代別傾向")
plt.xlabel("年代")
plt.ylabel("ジャンル")
plt.tight_layout()
plt.savefig('../figures/genre_trends_highrated.png', dpi=300)
plt.show()
```
![genre_trends_highrated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692857/bb12e8ba-dc7d-4004-b54c-851b20327e52.png)
ヒートマップの上限が20分の1以下になりました。

### 正規化
前述の可視化だと作品数が多い近年のみが濃くなってしまいます。
したがって、各年代ごとに正規化します。
```python
# 行（各年代）ごとに合計を1にする（＝各ジャンルの相対的な割合）
genre_decade_normalized = genre_decade_highrated.div(genre_decade_highrated.max(axis=1), axis=0)

plt.figure(figsize=(12, 8))
sns.heatmap(genre_decade_normalized.T, cmap='YlGnBu', linewidths=0.5)
plt.title("年代ごとに正規化したジャンル分布（高評価映画）")
plt.xlabel("年代")
plt.ylabel("ジャンル")
plt.tight_layout()
plt.savefig('../figures/genre_trends_highrated_normalized.png', dpi=300)
plt.show()
```
![genre_trends_highrated_normalized.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692857/62333b27-adab-47ab-8d2b-6a164e09be85.png)

[Drama(≒ヒューマンドラマ)](https://www.imdb.com/interest/in0000076/?ref_=tt_ov_in_8)が全ての年代で最大値をとっていることが分かりました。
IMDbでは一つの映画に複数のジャンルがラベルづけされています。
Dramaが人気というよりも、むしろ多くの映画にDramaというラベルをつけやすいことが全年代での多さの理由ではないかと考えています。

## おわりに
今回はChatGPTに教わりながらプログラミングをしました。
私がやりたいことを投げかけることでコード例が生成されることは面白かったです。
![x.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692857/539d9a1e-f2be-406f-b10e-590de3242a04.png)

また、「さらに～することで～できます」というような次につながる提案をしてくれることは作業を進めやすくしてくれました。
![y.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692857/b74932c9-3157-452b-897a-ccd29e741022.png)



今回の分析はかなり粗いですが、ある程度の形にはなったので満足しています。
引き続きChatGPTに教わりながらいろいろやってみます。