Zennのタグ情報を分析してみた

# 概要
QiitaとZennってどんな特徴があるの？
前回Zennのスクレイピングを投稿したのですが、
そちらで取得した情報をPythonを用いて時系列順に分析してみました。


# 目次
[1.タグ情報の時系列グラフ](#1タグ情報の時系列グラフ)
[2.実行環境](#2実行環境)
[3.コード概要](#3コード概要)
[4.感想](#4感想)
[5.最後に](#最後に)

# 1.タグ情報の時系列グラフ
結果はこのようなグラフが得られました。
タグ情報の全体に占める割合の移動平均をとったグラフになります。
![ratio_m_ave_half_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/f8d5d5f8-f96f-cccf-6dae-9e9a34777ae9.png)


# 2.実行環境
今回、Google Colaboratoryで実施しました。

# 3.コード概要
前回のスクレイピングで作成したcsvファイルを読み込みます。
まずはgoogle driveとの連携を作成します。
```python
from google.colab import drive
drive.mount('/content/drive')
```
csvファイルを読み込みます。
pandasを利用して読み込みます。
```python
import pandas as pd
#fileNoを定義
target_read_file = '/content/drive/MyDrive/csv/Zenn_allinfo_20220210_all.csv'
#csvからページ名を取得
df = pd.read_csv(target_read_file)
#データを確認
df.head(1)
```

下記の様なデータフレームが取得できます。

||title　|author　|link　|time|tag_1|tag_2|tag_3|tag_4|tag_5|tag_6|tag_7|tag_8|tag_9|tag_10|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|0　|...|...|...|...|...|...|...|...|...|...|...|...|...|...|

分析するにあたり、tag情報が1~10まで別のカラムに格納されているのが、都合が悪いので、
一つのtagというカラムにまとめます。

```python
# 変数d_listに空のリストを作成する
d_list = []
#処理数nを定義
n=0
for time,tag_1,tag_2,tag_3,tag_4,tag_5,tag_6,tag_7,tag_8,tag_9,tag_10 in zip(df.time,df.tag_1,df.tag_2,df.tag_3,df.tag_4,df.tag_5,df.tag_6,df.tag_7,df.tag_8,df.tag_9,df.tag_10):
    d1 = {'time':time,
            'tag': tag_1}
    d_list.append(d1)
    d2 = {'time':time,
            'tag': tag_2}
    d_list.append(d2) 
    d3 = {'time':time,
            'tag': tag_3}
    d_list.append(d3) 
    d4 = {'time':time,
            'tag': tag_4}
    d_list.append(d4)
    d5 = {'time':time,
            'tag': tag_5}
    d_list.append(d5) 
    d6 = {'time':time,
            'tag': tag_6}
    d_list.append(d6) 
    d7 = {'time':time,
            'tag': tag_7}
    d_list.append(d7) 
    d8 = {'time':time,
            'tag': tag_8}
    d_list.append(d8) 
    d9 = {'time':time,
            'tag': tag_9}
    d_list.append(d9)                              
    d10 = {'time':time,
            'tag': tag_10}
    d_list.append(d10) 

# 変数d_listを使って、データフレームを作成する
df = pd.DataFrame(d_list)            
df.head(10)  

```

||time|tag|
|---|---|---|
|0|2022-02-14T00:42:44+00:00|Firebase|
|1|2022-02-14T00:42:44+00:00|TypeScript|
|2|2022-02-14T00:42:44+00:00|frourio|
|3|2022-02-14T00:42:44+00:00|railway|
|4|2022-02-14T00:42:44+00:00|tech |
|5|2022-02-14T00:42:44+00:00|NaN|
|6|2022-02-14T00:42:44+00:00|NaN|
|7|2022-02-14T00:42:44+00:00|NaN|
|8|2022-02-14T00:42:44+00:00|NaN|
|9|2022-02-14T00:42:44+00:00|NaN|

Null値を削除します。

```python
df2 = df[df['tag'].notnull()]
df2.head(10)
```
||time|tag|
|---|---|---|
|0|2022-02-14T00:42:44+00:00|Firebase|
|1|2022-02-14T00:42:44+00:00|TypeScript|
|2|2022-02-14T00:42:44+00:00|frourio|
|3|2022-02-14T00:42:44+00:00|railway|
|4|2022-02-14T00:42:44+00:00|tech|
|10|2022-02-14T00:42:24+00:00|JavaScript|
|11|2022-02-14T00:42:24+00:00|React|
|12|2022-02-14T00:42:24+00:00|Vue.js|
|13|2022-02-14T00:42:24+00:00|SWR|
|14|2022-02-14T00:42:24+00:00|tech|

Null値が消えました。


続いてどういったタグがあるか確認します。
```python
df2['tag'].value_counts()
```
```
tech             23387
idea              3547
JavaScript        1893
TypeScript        1671
Python            1656
                 ...  
研鑽                   1
セルフマネジメント            1
webex                1
回帰                   1
githubprofile        1
Name: tag, Length: 9000, dtype: int64
```
確認すると、「tech」が圧倒的に多く次いで、「idea」というタグが多いという結果です。
これはZennの仕様上、techかideaのどちらかのタグをつけるためなので、
これら二つのタグは省いて処理していきます。

またそのままですと日本語表示がされないので、
「japanize-matplotlib」というライブラリをインポートして活用します。

```python
!pip install japanize-matplotlib
import japanize_matplotlib
import matplotlib.pyplot as plt

df2['tag'].value_counts()[2:22].plot.bar()
plt.title('ランキングALL')
```

value_counts()[2:22]とすることで、3位から22位までの20ランキングを取得します。

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/f897fecf-924d-d385-7600-a48927f084d0.png)

次にタグ情報の移り変わりを時系列で整理します。
月単位で集計しようと思うので、月単位の日時情報でまとめます。

```python
df2['date']=pd.to_datetime(df2['time']).dt.strftime('%Y-%m')
df2.head()
```
||time|tag|date|
|---|---|---|---|
|0|2022-02-14T00:42:44+00:00|Firebase|2022-02|
|1|2022-02-14T00:42:44+00:00|TypeScript|2022-02|
|2|2022-02-14T00:42:44+00:00|frourio|2022-02|
|3|2022-02-14T00:42:44+00:00|railway|2022-02|
|4|2022-02-14T00:42:44+00:00|tech|2022-02|

タグの数は月毎に総数が異なるため、3か月毎に移動平均をとります。
まず合計タグ数の移動平均を作成し、タグ数／合計タグ数を行うことでデータを偏りを減らします。

まず合計の移動平均を算出して、データフレームを作成し、
mergeで合算して必要なデータフレームを作成します。

```python
df_sum=df2[['date','tag']].groupby(['date']).count()
df_sum.columns=['sum']

#移動平均
w_size=3
v = np.ones(w_size) / w_size
# a=df_sum['sum']
out = df_sum.apply(lambda x: np.convolve(x, v, mode='same'),axis=0)
out.columns=['sum_m_ave']

b=pd.DataFrame(out,columns=['sum_m_ave'])

df_sum2 = df_sum.merge(b,left_index=True, right_index=True, how='left')
```
できあがったセルはこのような形になります。
```python
print(df_sum2)
```
|date|sum|sum_ave|
|---|---|---|
|2020-09|3585|2858.333333|
|2020-10|4990|4145.000000|
|2020-11|3860|5095.666667|
|2020-12|6437|5245.000000|
|2021-01|5438|5679.333333|
|2021-02|5163|5495.333333|
|2021-03|5885|5468.666667|
|2021-04|5358|5806.000000|
|2021-05|6175|5589.000000|
|2021-06|5234|5617.333333|
|2021-07|5443|5324.000000|
|2021-08|5295|5483.000000|
|2021-09|5711|5351.000000|
|2021-10|5047|5270.666667|
|2021-11|5054|6587.666667|
|2021-12|9662|7275.333333|
|2022-01|7110|6783.666667|
|2022-02|3579|3563.000000|

続いて個別のタグ情報も移動平均を算出し、
辞書型でデータを格納します。
```python
result = {}
for i, t in enumerate(df2['tag'].value_counts().index):
    #tag情報と同一のデータのみで集計
    df_tmp = df2[df2['tag']==t]
    df_gb = df_tmp.groupby('date').count()
    df_gb = df_gb.drop('time',axis=1)

    #移動平均算出
    w_size=3
    v = np.ones(w_size) / w_size
    out = df_gb.apply(lambda x: np.convolve(x, v, mode='same'),axis=0)
    out.columns=['m_ave']
    df_m = df_gb.merge(out,left_index=True, right_index=True, how='left')
    df_m = df_m.merge(df_sum2,left_index=True, right_index=True, how='left')

    #割合算出
    df_m['ratio'] = df_m['tag'] / df_m['sum']
    df_m['ratio_m_ave'] = df_m['m_ave'] / df_m['sum_m_ave']

    #結果を格納
    r={'rank':i+1, 'df':df_m}
    result[t] = r
```

この結果をグラフ化します。
まずは単純な割合でグラフ化します。
グラフ作成時、横軸がかぶってしまって見えなくなってしまったので、
下記ページを参考にしました。

https://www.delftstack.com/ja/howto/matplotlib/how-to-rotate-x-axis-tick-label-text-in-matplotlib/#xticks-%25E3%2583%25A9%25E3%2583%2599%25E3%2583%25AB%25E3%2583%2586%25E3%2582%25AD%25E3%2582%25B9%25E3%2583%2588%25E3%2582%2592%25E5%259B%259E%25E8%25BB%25A2%25E3%2581%2599%25E3%2582%258B-plt.xticksrotation

```python
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
plt.ylabel('ratio',size=16)
#x軸ラベルを90度回転
plt.xticks(rotation=90)

i = 1
for k, v in result.items():

    rank = v['rank']
    df_tmp = v['df']

    if rank < 3:
        continue
    if rank > 22:
        continue
    plt.plot(df_tmp['ratio'],label=k)


plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)
plt.show()
```


![ratio_all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/3010e2d0-5378-f2ff-e56a-ecfad2334046.png)


グラフの凡例数が多くて見づらいので、
凡例数を10個ずつにして、再度出力します。

```python

plt.figure(figsize=(10, 10))
plt.ylabel('ratio',size=16)

#x軸ラベルを90度回転
plt.xticks(rotation=90)

i = 1
for k, v in result.items():
#    plt.clf()

    rank = v['rank']
    df_tmp = v['df']

    if rank < 3:
        continue
    if rank > 22:
        break
    plt.plot(df_tmp['ratio'],label=k)
    if i % 10 == 0:
        plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)
        plt.show()
        plt.clf()
        if i // 10 < 2:
            plt.figure(figsize=(10, 10))
            plt.ylabel('ratio',size=16)
            #x軸ラベルを90度回転
            plt.xticks(rotation=90)
    i += 1
```

![ratio_half_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/aa7dcfe3-66f3-0116-5ae1-0471f6684eca.png)

![ratio_half_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/3407adea-64da-d66e-7919-0ce2ece1a4d6.png)


つづいて、移動平均での割合グラフを出力します。

全体グラフ出力
```python
plt.figure(figsize=(10, 10))
plt.ylabel('ratio_m_ave',size=16)

#x軸ラベルを90度回転
plt.xticks(rotation=90)

i = 1
for k, v in result.items():


    rank = v['rank']
    df_tmp = v['df']

    if rank < 3:
        continue
    if rank > 22:
        continue
    plt.plot(df_tmp['ratio_m_ave'],label=k)

plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)
plt.show()

```

![ratio_m_ave_all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/142c2249-bcc5-200d-4653-2afc53d9c773.png)

個別グラフ出力
```python
plt.figure(figsize=(10, 10))
plt.ylabel('ratio_m_ave',size=16)

#x軸ラベルを90度回転
plt.xticks(rotation=90)

i = 1
for k, v in result.items():
#    plt.clf()

    rank = v['rank']
    df_tmp = v['df']

    if rank < 3:
        continue
    if rank > 22:
        continue
    plt.plot(df_tmp['ratio_m_ave'],label=k)
    if i % 10 == 0:
        plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)
        plt.show()
        plt.clf()
        if i // 10 < 2:
            plt.figure(figsize=(10, 10))
            plt.ylabel('ratio_m_ave',size=16)
            #x軸ラベルを90度回転
            plt.xticks(rotation=90)

    i += 1
```





![ratio_m_ave_half_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/f8d5d5f8-f96f-cccf-6dae-9e9a34777ae9.png)


![ratio_m_ave_half_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532908/e016b5ed-e29f-e98a-3a50-a4239f70ad00.png)


# 4.感想
移動平均にしてみると、上位層（TOP5）が安定していることがわかりました。
また、初めは初心者が２位だったにも関わらず、順位をかなり落としており、
玄人感が特徴として出ているのかなと感じました。

# 5.最後に
タグ情報をグラフかして見える化してみましたが、
視覚化すると見えてきたり、移動平均をとってみると、
違った見方ができて面白いなと思いました。

まだまだ、難しいですがそういったことが、
プログラム上でできてしまう、pythonは改めて便利だなとも感じました。