>開発>python>[Pythonで統計]最小二乗法

最小二乗法

最小二乗法は、気温が高いとビールの購買数が高くなり、気温が低くなると購買数が減るなど、データを分析し将来の顧客行動を予測するなど、一定の条件下で将来のデータを予測する際に用いられる。このように、相関関係のあるデータから特定の条件の値を予測することを回帰といい、求めたい値を目的変数、予測する元となる値を説明変数という。

二乗和誤差 : \( \displaystyle MSE = |sum_{i = 1}^{n} (y_i – f(x_i))^2 \)

\( \displaystyle {Cov_{x, y}} \)は、xとyの共分散、\(\displaystyle \sigma \)は分散を表す。

回帰直線 : \(\displaystyle f(x) = ax + b \)、\(\displaystyle a = \frac{Cov_{x, y}}{\sigma_x^2} \)、\(\displaystyle b = (\bar{y} – a\bar{x}) \)

Pythonで回帰直線を求める

説明変数\(\displaystyle (x) \)を身長とし、体重を目的変数\(\displaystyle (y) \)として予測するモデルを一次関数で表すと次のようになる。\(\displaystyle a \)は傾き、\(\displaystyle b \)は切片となる。傾きと切片が決まれば直線が決まるため、もっともそれらしい直線を決めていく。

\(\displaystyle f(x) = ax + b \)

NBA選手の身長で185cmから230cmの選手の対数正規分布を求めてみる。データセットはKaggleでMuhammet Ali Büyüknacarさんの”2021-22 NBA Season Active NBA Players“を利用させていただく。

import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# NBA選手のデータ
players = pd.read_csv('データセットのcsvのパス')
# フィートからセンチへ
heights = players['Height_i'] / 0.032808
# ポンドからキログラムへ
weights = players['Weight'] / 2.2046

# 散布図(縦軸:体重、横軸:身長)
plt.scatter(heights, weights, s=25)
plt.xlim(160, 240)
plt.ylim(40, 180)

# 16ocmから240cmの範囲
x = np.arange(160, 240)

# モデル1
a1 = 1.0
b1 = 100
model1 = a1 * x - b1
plt.plot(x, model1, color='r')

# モデル2
a2 = 1.5
b2 = 180
model2 = a2 * x - b2
plt.plot(x, model2, color='g')

# モデル3
a3 = 2.0
b3 = 260
model3 = a3 * x - b3
plt.plot(x, model3, color='b')

直感的に赤の線がもっともそれらしい直線だとわかる。

次は、直感的ではなく数値で評価してみる。

import copy
import pandas as pd

def rss(data):
  """
  二乗和誤差
  param data  : データ
  return      :  二乗和誤差
  """
  result = 0

  for i in data.iterrows():
      result += (i[1]['weight'] - i[1]['prediction']) ** 2

  return result

# NBA選手のデータ
players = pd.read_csv('/content/drive/MyDrive/nba_players.csv')
# フィートからセンチへ
heights = players['Height_i'] / 0.032808
# ポンドからキログラムへ
weights = players['Weight'] / 2.2046

df = pd.DataFrame({'height':heights, 'weight':weights})

# モデル1
a1 = 1.0
b1 = 100
rss_model1 = copy.deepcopy(df)
rss_model1['prediction'] = a1 * rss_model1['height'] - b1

# モデル2
a2 = 1.5
b2 = 180
rss_model2 = copy.deepcopy(df)
rss_model2['prediction'] = a2 * rss_model2['height'] - b2

# モデル3
a3 = 2.0
b3 = 260
rss_model3 = copy.deepcopy(df)
rss_model3['prediction'] = a3 * rss_model3['height'] - b3

# 指数表示無効化
pd.options.display.float_format = '{:.2f}'.format
result = pd.DataFrame([[rss(rss_model1)], [rss(rss_model2)], [rss(rss_model3)]], index=['モデル1', 'モデル2', 'モデル3'], columns=['RSS'])
二乗和誤差
モデル187533.00
モデル2336902.46
モデル31015713.48

二乗和誤差は、値が低い方が精度が高いので、モデル1が最も精度が高く、モデル3が最も精度が低いことになる。グラフを見た時の直感と同じような結果が出た。

最小二乗法で求める

次は、最小二乗法で二乗和誤差が最も少ない直線を求める。

\(\displaystyle f(x) = ax + b \)

\(\displaystyle a = \frac{Cov_{x, y}}{\sigma_x^2} \)

\(\displaystyle b = (\bar{y} – a\bar{x}) \)

import copy
import pandas as pd

def cov(x, y):
  """
  共分散
  param x : 標本x
  param y : 標本y
  return  : 共分散
  """
  # 相加平均
  x_mean = np.mean(x)
  y_mean = np.mean(y)

  # 要素数
  n = len(x)

  c = 0.0

  for i in range(n):
    x_i = x[i]
    y_i = y[i]
    c += (x_i - x_mean) * (y_i - y_mean)

  # 共分散
  cov = c / n

  return cov

def std(x):
  """
  標準偏差
  param x : 標本
  return  : 標準偏差
  """
  # 相加平均
  mu = np.mean(x)

  # 要素数
  n = len(x)

  std = 0.0

  # 平均と要素の差を取って2乗する
  for i in range(n):
    std += (x[i] - mu) ** 2

  std = std / n
  sigma = np.sqrt(std)

  return sigma

def slope(x, y):
  """
  傾き
  param x : 標本x
  param y : 標本y
  return  : 傾きa
  """
  a = cov(x, y) / (std(x) ** 2)
  return a

def intercept(x, y):
  """
  切片
  param x : 標本x
  param y : 標本y
  return  : 切片b
  """
  # 相加平均
  x_mean = np.array(x).mean()
  y_mean = np.array(y).mean()

  # 傾き
  a = slope(x, y)

  # 切片
  b = y_mean - x_mean * a

  return b

# NBA選手のデータ
players = pd.read_csv('/content/drive/MyDrive/nba_players.csv')
# フィートからセンチへ
heights = players['Height_i'] / 0.032808
# ポンドからキログラムへ
weights = players['Weight'] / 2.2046

df = pd.DataFrame({'height':heights, 'weight':weights})

# 傾きa
a = slope(df['height'], df['weight'])
# 切片b
b = intercept(df['height'], df['weight'])

# x軸の範囲
x = np.arange(160, 240)
# 予測された目的変数
y = a * x + b

# 回帰直線プロット
df.plot(kind='scatter', x='height', y='weight')
plt.xlim(160, 240)
plt.ylim(40, 180)
plt.plot(x, y, color='red')
62398.249909167396

先程のモデル1の直線とどちらがそれっぽい直線かは直感的にはわかりにくいが、二乗和誤差は”87533.00″から”62398.24″と減っているので精度が上がっている。

数学

記事を読んでいただきありがとうございました。

Page Top