Python(pandas)を使って基本統計量から単回帰分析まで統計の復習

# はじめに

機械学習やDeeplearningなどを勉強していると実装モデルに目がいきがちですが、データを扱う以上、基本的な統計を知っておく必要があります。また、わざわざ難しいモデルを持ち出さなくても集計レベルでもわかることがたくさんあります。
と言いつつ、大学で勉強したことを忘れているので、手元にあった参考書を片手にPythonとそのライブラリであるpandasを使って統計の初歩を復習しようと思います。
たまたま、データ共有プラットフォーム「delika」のデータに関する記事を書こう！キャンペーンがあったので、それに乗っかることにしました。

# ゴール

CSV形式の数値データに対して、基本統計量の出力、集計、散布図、単回帰分析まで行い、統計的手法の基本をおさらいします。
※個別の詳細は公式ドキュメントへのリンクを貼ります。

delikaを使う
https://delika.io/qiita_delika_article_campaign/QiitadelikaDummy/articles.csv

# 実行環境

- Windows10
- Python 3.8.5
- pandas 1.3.5
- matplotlib 3.2.2  
- seaborn 0.11.0  
- scikit-learn 0.23.2

# ライブラリのインポート

pandasとは、Pythonのデータ分析ライブラリの1つであり、様々なデータを共通の型(DataFrame)で処理でき、パフォーマンスも最適化されています。表計算や、抽出などSQLのようなことが実現可能です。
特徴: https://ja.wikipedia.org/wiki/Pandas

matplotlibとは、Pythonでグラフ化、可視化ができるライブラリです。pandasで集計した結果を可視化することが可能です。今回は、matplotlib内のpyplotモジュールを使用します。このモジュールを使うことで、データを引き渡すだけでmatplotlibの機能を使って、自動的に必要なグラフを生成してくれます。
公式：https://matplotlib.org/

※seaborn、scikit-learnというライブラリも使いますが、後述します。

```Python
import pandas as pd
import matplotlib.pyplot as plt
```

# 統計入門

## データの入力

### 使用するデータ

delika：Qiitaの投稿に関するダミーデータの内、記事に関するデータ（articles.csv）を使用します
https://delika.io/qiita_delika_article_campaign/QiitadelikaDummy/articles.csv

### DataFrameとして読み込み

DataFrameとはPandasで扱うことのできる表形式のデータ(2次元)である。
公式ドキュメント: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html

```Python
# CSVデータを読み込む
df = pd.read_csv('articles.csv')
# 読み込んだデータの最初の5行を出力
df.head()
```

## 基本統計量

データの型と、それぞれの数値項目の基本統計量を計算してみます。

```Python
# カラム別のデータ型
df.info()
"""　出力
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000 entries, 0 to 999
Data columns (total 7 columns):
 #   Column            Non-Null Count  Dtype 
---  ------            --------------  ----- 
 0   id                1000 non-null   int64 
 1   created_at        1000 non-null   object
 2   likes_count       1000 non-null   int64 
 3   comments_count    1000 non-null   int64 
 4   url               1000 non-null   object
 5   users             1000 non-null   object
 6   page_views_count  1000 non-null   int64 
dtypes: int64(4), object(3)
"""

# 数値カラムの基本統計量
df.describe()
"""　出力
		id	    	likes_count	comments_count	page_views_count
count	1000.000000	1000.000000	1000.000000		1000.000000
mean	500.500000	592.138000	5.107000		4923.165000
std		288.819436	346.712903	3.207778		2891.506148
min		1.000000	0.000000	0.000000		4.000000
25%		250.750000	290.750000	2.000000		2461.000000
50%		500.500000	584.500000	5.000000		4646.000000
75%		750.250000	888.000000	8.000000		7503.750000
max		1000.000000	1200.000000	10.000000		10000.000000
"""
```

IDも数値扱いなので、基本統計量が出力されています。

## 集計

### ユーザ数を求める

SQLのGROUP BY句と同様に、pandasにもgroupbyがあります。

```Python
# usersで軸として、数を数える
df.groupby('users').count()
"""　出力
	id	created_at	likes_count	comments_count	url	page_views_count
users						
#NAME?	15	15	15	15	15	15
-3C9Qpzb0	1	1	1	1	1	1
-6ZyxjZ08	1	1	1	1	1	1
-9LoOpKe4	1	1	1	1	1	1
-WUM6h6d-	1	1	1	1	1	1
...	...	...	...	...	...	...
"""
```

「#NAME?」で15人となっているので、EXCELで開いて保存した結果がdelikaのサンプルデータになっているようです。それ以外は、1なので、記事の執筆者はバラバラということがわかりました。

### 年別の投稿数を求める

時系列処理もpandasは得意です。groupbyではなく、resampleを使って、日別を月別、年別に変換して集計できます。

```Python
# データを読み込んだ時に、Object型だったので、日付をdatetime64型に変換する
df['created_at'] = pd.to_datetime(df['created_at'])
# 日付をIndexに設定する
df_date = df.set_index('created_at')
# 月別（引数はM）
df_date.resample('M').sum()
""" 出力
	id	likes_count	comments_count	page_views_count
created_at				
2012-04-30	2291	3470	35	31789
2012-05-31	5868	5416	64	63187
2012-06-30	3407	2240	26	27859
2012-07-31	4119	3355	22	27170
2012-08-31	4295	4418	22	43066
・・・
"""
# 年別（引数はY）
df_date.resample('Y').sum()
""" 出力
	id	likes_count	comments_count	page_views_count
created_at				
2012-12-31	42380	46878	395	385064
2013-12-31	49853	62701	529	440000
2014-12-31	53258	59236	516	478362
2015-12-31	51158	58024	591	480729
・・・
"""
```

公式ドキュメント: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html

## 可視化

### ヒストグラム

データの全体像を観察するために使用されるグラフです。それぞれの値がどのぐらい出現するかを示します。どういう値を取りやすいか、どのくらいばらついているかの分布になります。下記は、ページビューとその回数をヒストグラムにしました。

```Python
# ページビューをヒストグラムにする
plt.hist(df["page_views_count"])
# 縦軸と横軸のラベル
plt.xlabel("page_views_count")
plt.ylabel("count")
# グリッド線の表示
plt.grid(True)
```

![482539bd-c78e-473e-a8de-d886a5ae8481.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119135/b6598670-aa81-1166-8f03-c3d8338d8b13.png)
ヒストグラムの各棒の範囲（ビンという）は自動でセットしてくれますが、調整も可能です。詳しくは公式ドキュメントを参照ください。

公式ドキュメント:
https://matplotlib.org/3.5.0/api/_as_gen/matplotlib.pyplot.hist.html

### 散布図

2つの組み合わせデータをxy座標平面上に表したグラフです。2つの変数間の関係性などが見えてきます。回帰分析をする前には必ず実施して、線形性があるか異常値などがないかなどを確認します。ここでは、ページビュー数とLGTM数の関係を散布図に落としてみます。

```Python
plt.plot(
    df['page_views_count'], # x軸
    df['likes_count'],      # y軸
    'o'                     # フォーマット/'o': 点、'x': バツ印
)
plt.ylabel('likes_count')
plt.xlabel('page_views_count')
plt.grid(True)
```

![857124c2-059f-4cf5-8dcd-4bf736f8fe76.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119135/5509874e-00c7-6a75-f7f0-e512ba2b834f.png)

相関はなく、ランダムな感じがします。
公式ドキュメント: https://matplotlib.org/3.5.0/api/_as_gen/matplotlib.pyplot.plot.html
※plotメソッドの引数を変えるとグラフの形状を変更できる。

### まとめて描くには？

変数が多いと、ヒストグラムや散布図を都度描いていくのが面倒になります。seabornというライブラリを使うと、一度にまとめて描いてくれます。

```Python
import seaborn as sns
sns.pairplot(df[['likes_count','comments_count','page_views_count']])
```

![4d28ab98-d4cb-4f0c-ba40-d558bab501d5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119135/6a8d5143-ff1a-ca1a-8d98-35d383e204f3.png)

comments_countは0から10までの離散値であることから、直線的な散布図になっています。

seabornもmatplotlibと同様にPythonで使える可視化ライブラリの一つです。上記のように一気に変数の関係性を把握する際に便利です。また、matplotlibに比べてキレイな感じのグラフを作ってくれます。

公式ドキュメント: https://seaborn.pydata.org/

## 単回帰分析

回帰分析、つまり、数値予測をしてみます。単回帰とは、1変数から1変数を予測します。線形（直線、1次関数）的な関係式を出力します。
具体的には、page_views_count（説明変数）からlikes_count（目的変数）を予測するために、page_views_countをx、likes_countをyとしたときにy=ax+bを満たすようなa,bを求めます。
Pythonでは、scikit-learnというライブラリを使うと簡単に回帰分析を行うことが可能です。

```Python
# scikit-learnの中のlinear_model（線形モデル）モジュール
from sklearn import linear_model
# 回帰分析を行うときのおまじない
reg = linear_model.LinearRegression()
# sklearnを使う時は、numpy形式に変換する
x = df.loc[:, ['page_views_count']].to_numpy()
y = df.loc[:, ['likes_count']].to_numpy()
# 回帰分析を実行
reg.fit(x, y)
# 結果の出力
print('回帰係数：', reg.coef_)
print('切片：', reg.intercept_)
print('決定係数：', reg.score(x, y))
plt.scatter(x, y)
plt.ylabel('likes_count')
plt.xlabel('page_views_count')
plt.plot(x, reg.predict(x))
plt.grid(True)
""" 出力
回帰係数： [[0.00071635]]
切片： [588.61129734]
決定係数： 3.569087050481201e-05
"""
```

![1720e3e0-a986-4849-9341-faaf6edba81d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119135/8e7c9c5e-5d0c-08a2-1413-4350e0e5e4b2.png)
回帰係数がほぼ0で、決定係数も0なので、無相関だと言えます。

# まとめ

色々、調べながらQiitaのデータを分析してみました。あくまでも手順を確認することを主眼に置いていたので、データからのインサイトは特に得られませんでした。与えられたデータでは、ページ見られても見られなくてもLGMTの数はランダム？でした。
一方で、データを与えられたときに、その特徴をつかむための最低限のやっておくべき手順とそのコードをまとめることができたと思います。
データを見たら、これらのことを試してから目的に応じて、分析手法を調べながら、実装してみようと思います。