「小説家になろう」の作品タイトルは、本当に長くなってるのか？ Pandasで確認しよう！

# ＜訂正のお知らせ＞
本文コードにトンチキなミスがあったので、内容を大幅に修正しました。
（2017/10/27）
結論も変わってます。ごめんなさい…

# イントロ

一頃、「最近のラノベは文章みたいなタイトルが付いてる」と話題になっていました。
国内最大手の小説投稿サイト、「小説家になろう」でも、その傾向は顕著です。
私は何年も「なろう」で小説読んでますが、うーん、確かに年々タイトルが長くなっていくような気がしますね。

とはいえ、「気がする」ではいけません。ささっと確かめてしまいましょう。
PythonとPandasライブラリを使えば、かなり簡単なお仕事です。
（2017/10/27追記　簡単だと思ってましたが、アホなミスをしてました。
お恥ずかしい……）


# なろう小説API

[こちら](http://dev.syosetu.com/man/api/)に公式の解説があります。
今回はざっくり、

- タイトル
- 初投稿日（世代管理に使う）
- 総合評価（小説に振られるポイント。ランキングの指標になる）

の３点を引っ張ってくることにします。
マニュアルによれば、ofパラメータに`t`, `gf`, `gp`を指定すればいいそうです。

# データ取得

```py
import pandas as pd
import requests
import numpy as np
import seaborn as sns
from scipy import stats
import matplotlib.pyplot as plt
%matplotlib inline


url = "http://api.syosetu.com/novelapi/api/"

# APIのパラメータをディクショナリで指定する
# この条件で、総合評価順でjson形式のデータを出力する
payload = {'of': 't-gp-gf', 'order': 'hyoka','out':'json'}

r = requests.get(url,params=payload)
x = r.json()

x
```


> 
```py
[{'allcount': 518855},
 {'general_firstup': '2012-11-22 17:00:34',
  'global_point': 410915,
  'title': '無職転生\u3000- 異世界行ったら本気だす -'},
 {'general_firstup': '2013-11-07 15:49:34',
  'global_point': 330557,
  'title': 'ありふれた職業で世界最強'},
 {'general_firstup': '2013-07-21 07:55:05',
  'global_point': 324413,
  'title': '謙虚、堅実をモットーに生きております！'},
 {'general_firstup': '2013-02-20 00:36:17',
  'global_point': 318810,
  'title': '転生したらスライムだった件'},
```

上手くデータを取得できたようですね。
先頭に`allcount`属性があり、全データ数を教えてくれます。
実際の作業では使いませんので、`x[1:]`みたいにして飛ばしましょう。

## データの確認と整形

実は「なろう小説API」、取得上限が500です。
`st`パラメータをいじると、先頭位置を変更できますが、これも2000が上限です。
まあ、2000あれば十分と考えて、ちょっとループを回しましょう。

### 2017/10/27 追記
最初、ループの外でディクショナリを定義してたんですが、そうするとパラメータが更新されず、毎回同じデータを取得してました…
（つまり、実質500件のみの分析になっていた）
後で別な分析をしてる時に気付きました。ううう……

```py
st = 1
lim = 500

data = []
while st < 2000:
    # ディクショナリはループ内で定義する。最初ループ外で定義するというアホなミスをしてた…
    #　デバックのため、パラメータにncode(小説コード。一意にならないとおかしい）を追加
    payload = {'of': 't-gp-gf-n', 'order': 'hyoka',
          'out':'json','lim':lim,'st':st}
    r = requests.get(url,params=payload)
    x = r.json()
    data.extend(x[1:])
    st = st + lim
df = pd.DataFrame(data)

df.head()
```

| general_firstup     | global_point | title                                  |
|---------------------|--------------|----------------------------------------|
| 2012-11-22 17:00:34 | 410915       | 無職転生　- 異世界行ったら本気だす -   |
| 2013-11-07 15:49:34 | 330557       | ありふれた職業で世界最強               |
| 2013-07-21 07:55:05 | 324413       | 謙虚、堅実をモットーに生きております！ |
| 2013-02-20 00:36:17 | 318810       | 転生したらスライムだった件             |
| 2013-06-01 20:37:58 | 307209       | 八男って、それはないでしょう！         |


これで、2000行のデータを持つデータフレームの出来上がりです。
ちょっと中身を確認してみましょう。
`df.info()`メソッドを使ってみます

>
```
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2000 entries, 0 to 1999
Data columns (total 3 columns):
general_firstup    2000 non-null object
global_point       2000 non-null int64
title              2000 non-null object
dtypes: int64(1), object(2)
memory usage: 47.0+ KB
```

うーん、ぱっと見では日付データに見える`general_firstup`列がオブジェクトになっちゃってますね。
これは日付データに変更しないといけません。
それに、タイトルも欲しいのは文字数です。こっちも処理しましょう。

```py
df['general_firstup'] = pd.to_datetime(df['general_firstup'])
df['year'] = df['general_firstup'].apply(lambda x:x.year)

df['title_len'] = df['title'].apply(len)
```

| general_firstup     | global_point | title                                  | year | title_len |
|---------------------|--------------|----------------------------------------|------|-----------|
| 2012-11-22 17:00:34 | 410915       | 無職転生　- 異世界行ったら本気だす -   | 2012 | 20        |
| 2013-11-07 15:49:34 | 330557       | ありふれた職業で世界最強               | 2013 | 12        |
| 2013-07-21 07:55:05 | 324413       | 謙虚、堅実をモットーに生きております！ | 2013 | 19        |
| 2013-02-20 00:36:17 | 318810       | 転生したらスライムだった件             | 2013 | 13        |
| 2013-06-01 20:37:58 | 307209       | 八男って、それはないでしょう！         | 2013 | 16        |

これで、タイトルの長さを示す`title_len`列と投稿開始年だけを抽出した`year`列が出来ました。

さっそく、タイトルの長さをチェックしてみましょう！

# データフレームを操作して、探索的に分析する

何はともあれ、`describe`メソッドです。
データのサマリをさくっと見れるので、本当によく使うメソッドですね。

```py
df['title_len'].describe()
```

> count    2000.000000
mean       17.362500
std        10.088755
min         2.000000
25%        10.000000
50%        15.000000
75%        23.000000
max        77.000000
Name: title_len, dtype: float64

面白い数字です。平均値が17字ですが、これは俳句の文字数と同一ですね。
つまり、なろうのタイトルは俳句だったのです！
古池や　蛙飛び込む　水の音…

## ヒストグラム

ちょっとデータを可視化して、分布を見てみましょう。
pandasには組み込みの`.plot`メソッドがありますし、ヒストグラム専用の`.hist`メソッドなんてものもあります。

```py
df['title_len'].hist()
```

![hist.png](https://qiita-image-store.s3.amazonaws.com/0/209238/f23964fc-f0ea-350d-2e73-274cf7ce4d18.png)



これだけ見ると、３０文字くらいでも普通なんじゃね？と思ってしまいますね。

## グループ集計

さて、今度は時系列で見ていきましょう。
pandasではSQLライクなgroupby集計が使えます。

```py
title_by_year = df.groupby('year')['title_len'].agg(['mean','count','std']).reset_index()
```

`.agg`で複数の集約関数を渡せるのがミソですね。
`.reset_index`メソッドをチェインしてますが、これをしないと集約条件である`year`カラムがindexになってしまうからです。
今回の分析では`year`カラムがindexのままだと面倒になるので`.reset_index`で普通のカラムに戻しています。
逆に、集約条件がindexじゃないと面倒なケースもあるでしょうから、ケースバイケースです。

さて、結果を見てみますと…

| year | mean      | count | std       |
|------|-----------|-------|-----------|
| 2008 | 7.500000  | 2     | 2.121320  |
| 2009 | 11.100000 | 10    | 6.983313  |
| 2010 | 9.750000  | 44    | 4.457160  |
| 2011 | 10.436893 | 103   | 5.071392  |
| 2012 | 12.527174 | 184   | 7.149406  |
| 2013 | 14.229008 | 262   | 8.526331  |
| 2014 | 15.785942 | 313   | 8.566979  |
| 2015 | 17.914787 | 399   | 9.554801  |
| 2016 | 20.497608 | 418   | 10.642490 |
| 2017 | 24.169811 | 265   | 11.097256 |

2010年までのデータは件数が少なすぎるので、削ったほうが良さそうですね。
しかし、うーむ……もうこれで答え出たようなもんです。
**どう見たってタイトルの長さは伸びてます。**
それに、標準偏差が広がっていく傾向が見られますね。タイトルの多様化でしょうか。
しかし、このまま行くと、俳句から和歌になってしまうのでは？

## 折れ線グラフ

```py3
title_by_year.plot(x='year',y='mean')
```
と、ワンライナーで折れ線グラフが描けます。

![line.png](https://qiita-image-store.s3.amazonaws.com/0/209238/feac6dd0-37dd-dfc1-8de3-28a1b4681386.png)

件数が少なすぎる初期は置いておいて、2011年から一貫して伸び続けてますね。
このまま行くと、どうなってしまうんでしょうか。
scipyの線形回帰メソッドで調べてみましょう。

```py
stats.linregress([0,1,2,3,4,5,6], title_by_year[title_by_year['year'] > 2010]['mean']) 
```

> LinregressResult(slope=2.172335756048652, intercept=9.991738901311642, rvalue=0.98925248013523381, pvalue=2.2867537033141338e-05, stderr=0.1435932030138164)

結果を見ますと、傾き(slope)が2.17で、切片(intercept)がほぼ10です。
y = 2.17x + 10ですね。
えーと、この調子だと来年には25文字になります。2021年には、31文字になって和歌になります。
最初、データ取得にミスが有り、先頭500件のみで計算していたので、2019年には和歌になってしまうぞ！という結論を出しましたが、修正されました。
なろうのタイトルが和歌になるのは、東京オリンピックの後です！

### 2017/10/27追記
ちなみに元の回帰式はy = 3x + 13.1でした。先頭500件だけで分析すれば、この結果になりますが、サンプルが少なすぎますよね。


# まとめ
ざっくり見てきましたが、「なろう」の小説タイトルが順調に伸び続けていることが確認できました。
pandasとpythonの各種ライブラリと組み合わせれば、簡単にデータ分析が行なえますね。
る、ループの使い方さえ間違えなければ……穴があったら入りたい……

# 追記
## 2017/10/26 23:50
人もすなるGitHubといふものを（略
えー、今回の内容をまとめてGistに上げました。

https://gist.github.com/dely13/fc8bcf5240ea189d019b82329d4f6a8e

です。参考までにどうぞ。