中学・高校数学でオイラーのγの近似値を求めるプログラミング数学×Pythonプログラミング入門(2/2 ページ)

» 2021年08月19日 05時00分 公開
[羽山博]
前のページへ 1|2       

4. 可処分所得の前年度比を求めるサンプルプログラム

 ここまで、総和(累計)や個数を求めるためのプログラミングについてざっと見てきました。リストと繰り返し処理は「相性」がいいので、多くの面倒な計算を自動化するのに役立ちます。ただ、意のままにプログラミングするには繰り返しの制御変数やリストのインデックスの操作に慣れる必要があります。

  • 制御変数: for i in range(0, n)iにあたる変数
  • リストのインデックス: data[i]iにあたる変数

 なおPythonでは、インデックスを使わなくてもfor x in dataという形式で、dataの個々の値を変数xに代入しながら繰り返し処理したり、インデックスが必要な場合は、for i, x in enumerate(data)のような形式で、インデックスiと個々の値xを取得しながら繰り返し処理したりできます。しかし今回はインデックスの操作について学ぶために、for i in range(0, n)のようにしてインデックスだけを取得しながら繰り返し処理するコードを書いてみることにします。

 さて、これまでのサンプルプログラムは繰り返し処理だけでできるものばかりで、インデックスを使ってリストを操作する例はありませんでした(頻度表の作成で少し触れましたが)。プログラミングの入門書などには、合計を求めたり、平均を求めたりするプログラムを例に、インデックスを使ってリストを操作する例がよくあります。以下のようなサンプルプログラムをご覧になった方も多いでしょう。

def average(x):
  sum = 0
  for i in range(0, len(x)):
    sum += x[i]
  return sum / len(x)

data = [67, 85, 78, 72]
average(data)
# 出力例:75.5

リスト9 平均値を求める
「全ての和の総和を求めて、個数で割る」という、数学での平均値の求め方に沿って素直に書くとこのようなコードになる。ただし、上でも記したように個々の値を取得する書き方(リスト3でも紹介した書き方)の方がPythonでは一般的。また、平均値を求めるだけなら、合計を求めるsum関数とリストの長さを求めるlen関数を使えば、sum(x)/len(x)という1行のコードで済む。実際のプログラミングでは、既に作られているものがあればそれを利用するのが得策だが、ここでは、インデックスの働きをおさらいするために、あえて上のような(冗長な)コードを示した。なお、ここではsumを変数名として使っているが、sum関数と同じ名前なので、同じ関数定義などの中でそれらを同時に使うとエラーになることに注意。

 しかし。しかし、です。この程度のプログラムなら理解もできるし、簡単に作ることもできるのに、別の問題を自分でプログラミングしようとすると途方に暮れてしまう、という人も多いようです。一体なぜでしょう。それは、恐らく制御変数とインデックスの対応が複雑になった場合にあまり慣れていないからだと思います。リスト9のプログラムだと制御変数iがそのままリストのインデックスとして使えたので、それらの対応について深く考えなくてもよかったからです。

 そこで、制御変数とリストのインデックスの対応を考える例を通して、それらの操作に慣れることにしましょう。いかにも数学的な例ばかりが続いているので、ちょっと気分を変えるために、日常生活に関わりの深いデータを使ってみます。

 以下のデータは、2007年から2020年までの勤労者世帯の可処分所得の推移です(出典は総務省統計局)。

勤労者世帯の可処分所得の推移 図2 勤労者世帯の可処分所得の推移
2018年〜2019年になってようやく2008年の水準に戻り、それ以降は上昇の傾向が見られる。しかし、データが「全世帯」ではなく「勤労者世帯」であることや、ここには示されていないが消費支出が減少していることなどから、一概に景気がよくなっているとは判断できないことに注意。

 このデータが以下のようなリストとして表されているものとします。

income = [402116, 402932, 383960, 389848, 380863, 383851, 380966, 381929, 381193, 376576, 382434, 400964, 416980, 431992]

 例えば、income[0]が2007年の可処分所得の値(つまり402116)です。

 このデータを基に、対前年度比(%)を最近の値から順に求めたリストincrを作ってみましょう(関数にしなくても構いません)。つまり、元のデータと順序が逆になるわけです。先に結果を示しておきます。

incr
# 出力例:[103.6, 104.0, 104.8, 101.6, 98.8, 99.8, 100.3, 99.2, 100.8, 97.7, 101.5, 95.3, 100.2]

リスト10 可処分所得の対前年度比のリスト
最近の値からのリストなので、incr[0]2020年の値/2019年の値*100となる。つまり、incr[0] = income[13]/income[12]*100となる。実際に計算してみると、431992÷416980×100=103.6となる。

 対前年度比(%)は以下の数式で表されます。数式そのものは簡単ですね。

  ある年の値 ÷ その前の年の値 × 100 (%)

 では、プログラミングに取り組みましょう。ここでは、これまでとちょっとやり方を変えて、まず、incrというリストをあらかじめ作っておき、先頭から順に結果を入れていくようにしてみましょう(appendメソッドは使いません。またreverseメソッドを使うとリストを逆順にできますが、練習のためなのでそれも使わないことにします)。

 というわけで、以下のコードの続きを考えてみてください。なお、小数点以下1桁で四捨五入するには、Pythonの標準組み込み関数であるround関数を使って、round(数値, 1)と書きます。

income = [402116, 402932, 383960, 389848, 380863
          383851, 380966, 381929, 381193, 376576
          382434, 400964, 416980, 431992]
incr = [0]*(len(income)-1) # incomeよりも1つ短いリストを作っておく
n = len(incr)
for i in range(n):
  # for文の内容を考える

incr # 結果の表示

リスト11 可処分所得の対前年度比を求めるプログラム(未完成)
for文の内容を考えてみよう。incomeの長さは14だが、incrの長さは13iの値は0n-1までとなる。

 いかがでしょう。対前年度比を求める数式は簡単なのに、for文の内容を考えると急にややこしくなった感じがしますね。そのような場合には、以下のポイントに留意しながら進めるといいでしょう。

  • 具体的な値(インデックス)を幾つか使って考え、一般化する
  • 端から「はみ出す」場合に注意する

 では、具体的に考えてみます。以下の図をご覧ください。

制御変数とインデックスの対応を考える 図3 制御変数とインデックスの対応を考える
iの値は0からn-1まで1ずつ増えていく。incrには順に結果を入れていくので、incr[0]incr[n-1]までを処理していけばいい。つまり、制御変数とインデックスは一致している。一方、incomeについては、i0のときはincome[13]income[12]を使って計算する。そのため、制御変数からインデックスを求めるルールを見付ける必要がある。

 制御変数iincrのインデックスは一致させればいいので、これに関しては難しくありませんね(incomeのインデックスと一致させて処理する方法もありますが)。問題はincomeのインデックスです。iの値が0のとき、income[13]income[12]を使って計算するので、01312に対応させてやる必要があります。また、iの値が1のとき、income[12]income[11]を使って計算するので、11211に対応させてやる必要があります。このようにして、いくつか具体例を考えてみると、一般的なルールを導き出す手がかりが得られます。つまり、以下のような例からルールを導き出せばいいということが分かります。

  (1) iの値が増えれば、incomeのインデックスが減る
  (2) i0番を使って、income13番にあたる値を取り出す
  (3) i0番を使って、income12番にあたる値を取り出す

 (1)から、制御変数の順序を逆転させてやれば、インデックスの順序に対応させられるということが分かります。そのためには、iの値にマイナスを付けてやればいいですね。

 (2)からは、13を足せばいいということが分かります。この13というのはnの値です。従って、-i+n、つまりn-iをインデックスとして使えばいいということが分かります。

 (3)からは、-in-1を足せばいいということが分かります。従って、n-i-1をインデックスとして使います。念のため図でも見ておきましょう。

制御変数とインデックスの対応を考える 図4 制御変数の逆順に処理する方法の例
制御変数の逆の順序でリストのインデックスを利用するには、制御変数にマイナスを付けて順序を逆転させ、先頭を合わせるためにリストの個数(=n)から引くというのが基本的。先頭がずれる場合は上の(3)のようにずらした数だけ引く(あるいは足す)とよい。

 以下の動画は、図4の動きを解説したものです(プログラミングではなく、図解の動画です)。ぜひ、参考にしてみてください。

動画4 制御変数とインデックスの取り扱い


 というわけで、

  incr[i]income[n-i]income[n-i-1]を使って計算した結果

とを求めればいいということが分かります。iの値は増えていきますがn未満(=12まで)なので、n-i-1の最小値は13−12−1=0です。従って、incomeの端からはみ出すこともありません。というわけで、プログラムを完成させましょう。

income = [402116, 402932, 383960, 389848, 380863
          383851, 380966, 381929, 381193, 376576
          382434, 400964, 416980, 431992]
incr = [0]*(len(income)-1)
n = len(incr)
for i in range(n):
  incr[i] = round(income[n-i] / income[n-i-1] * 100, 1) # これが答え

incr
# 出力例:[103.6, 104.0, 104.8, 101.6, 98.8, 99.8, 100.3, 99.2, 100.8, 97.7, 101.5, 95.3, 100.2]

リスト12 可処分所得の対前年度比を求めるプログラム(完成例)
インデックスの扱いさえ分かれば、数式に沿ってコードを書くだけ。小数点以下1桁まで求めるので、round関数を使って四捨五入している。

 今回取り扱ったリストはインデックスが1つだけの(1次元の)例ばかりでした。今後、行列などの計算を行う場合には、リストが二次元以上になることもあるので、制御変数とインデックスの対応を2つ以上考えなければなりません。しかも、ある制御変数がリストの行を表したり列を表したりすることがあるので、頭が付いていかなくなることがあるかもしれません。その場合でも、上で述べたような「具体例から一般化する」「はみ出す場合に注意する」といったアプローチが助けになるのではないかと思います。ですが、そういった事例については、また回を改めて。

 というわけで、まだまだ基礎固めのレベルですが、もう少し制御変数やインデックスの取り扱いに慣れるため、ぜひ以下の練習問題に取り組んでみてください。多少難しいな、と感じても一歩ずつ丁寧に考えていけば必ずできます。

練習問題

(1)ネイピア数(e)の近似値を求める

 ネイピア数(自然対数の底e2.71828...)は、さまざまな形で表されますが、以下の定義もその一つです。nの値を指定して、eの近似値を求める関数myexpを作成してみてください。

(ヒント) n!nの階乗を表します。例えば、4!=4×3×2×1=24です。階乗はmathモジュールに含まれているfactorial関数を使えば求められます。

import math
math.factorial(4)
# 出力例:24

リスト13 階乗を求めるにはmathモジュールのfactorial関数を使うとよい

 myexp関数を作成し、例えば、10を与えて呼び出すと以下のような結果になります。

myexp(10)
# 出力例:2.7182815255731922

リスト14 ネイピア数の近似値を求める関数myexpの実行例

 ちなみに、n!nが少し大きくなるだけで、結果がとても大きくなります。例えば、10!=3628800で、20!=2432902008176640000です。従って、1/n!の値はすぐにとても小さな値になります。というわけで、関数myexpにはそれほど大きな値を与えなくても(今回のように10でも)良い近似値が得られます。

(2)共分散を求める

 共分散を求める関数covarを作成してみましょう。共分散は以下の式で求められます。

 ただし、X={xi},Y={yi}とし、

はそれぞれX,Yの平均、nはデータの個数とします(ここでは、Xの個数とYの個数は同じものとするので、Xの個数を使えばよい)。

 共分散はXYの関係を表す値で、Xが増えればYも増える場合は正の大きな値になります。逆にXが増えればYが減る場合には負の大きな値になります。例えば、aを気温のデータとし、bをビールの売上本数とし、

  a = [12.1, 15.3, 18.6, 21.7, 26.1, 32.1]
  b = [45, 520, 2864, 6874, 25487, 102870]

というデータが与えられたとき、関数covarの実行結果は以下のようになります(気温が上がるとビールの売り上げも増える)。

a = [12.1, 15.3, 18.6, 21.7, 26.1, 32.1]
b = [45, 520, 2864, 6874, 25487, 102870]
covar(a, b)
# 出力例:211454.23333333337

リスト15 共分散を求める関数covarの実行例

(ヒント) コードは以下のように途中まで書かれているので、続きを書いてみてください。なお、平均値はリスト9のaverage関数を使って求めています。

def covar(x, y):
  n = len(x) # データの個数
  avex = average(x) # xの平均
  avey = average(y) # yの平均
  sumxy = 0 # 合計

  # この部分を書く

リスト16 共分散を求める関数covarの定義を完成させよう

(3)株価のn日移動平均を求める

 以下のデータは2021年3月22日〜8月11日までのファイザー社の株価(終値、単位ドル)の一覧です。これらのデータを基に、n日移動平均を求め、そのリストを返す関数movingaverageを作成してください。

ファイザー社の株価(終値)の5日移動平均と15日移動平均 図5 ファイザー社の株価(終値)の5日移動平均と15日移動平均
5日移動平均とは、直前の5日間の平均のこと。例えば、3月26日の35.11という値は、3月22日〜3月26日の5日間の平均。また3月29日の35.23は3月23日〜3月29日の5日間の平均(土日などを挟むので日付は連続していないが5日分)。このように、5日間の値を1日ずつずらしながら順に平均を求めていく。短期移動平均が長期移動平均を下から上に抜ける箇所はゴールデンクロスと呼ばれ、株価上昇のシグナルとなる。逆に、短期移動平均が長期移動平均を上から下に抜ける箇所はデッドクロス(またはデスクロス)と呼ばれ、株価下落のシグナルとなる。

(ヒント) 以下のコードを利用すれば、株価データを取得し、dataというリストに入れることができます。pandas_datareaderは、インターネット上で公開されているデータをデータフレームと呼ばれるデータ構造に読み込むためのライブラリです。これを使った株価データ取得の詳細については、今回のテーマから外れるのでここでは省略しますが、興味のある方は「「Python」と「Google Colaboratory」で株価データ分析に挑戦:「Python」×「株価データ」で学ぶデータ分析のいろは(1) - @IT」を参照してください。

!pip install pandas-datareader

import pandas_datareader.data as pdr
df = pdr.DataReader("PFE.US","stooq").sort_index()
data = df.loc['2021-3-22':'2021-08-11', 'Close' ].tolist()

data
# 出力例:[35.329, 34.701, 34.946, ..., 46.31]

リスト17 Stookから株価データを取得するコードの例
Google Colabを使っている場合はpandas_datareaderがあらかじめインストールされているので、1行目はなくても構わない(書いても構わないが、必要なものは既にあるというメッセージが表示される)。ここでは、pandas_datareaderDataReaderメソッドを使って、ファイザーの株価(PFE.US)をStooqと呼ばれるサイトから取得している。取得されたデータは日付の新しい順になっているので、sort_indexメソッドを使って日付の古い順に並べ替え、2021年3月22日〜8月11までの終値(Close)を取り出し、リストにしている。

 これでデータが用意できました。次に、移動平均を求めるためのデータを順に取り出します。このとき、リストのインデックスに[下限:上限]を指定すると、下限以上、上限未満のインデックスを持つ要素をリストとして取り出せます(この機能をスライスと呼びます)。

 例えば、x = [10, 20, 30, 40]のとき、x[0:3]とすると、[10, 20, 30]というリストが返されます。0:30以上3未満という意味になります(0番から3個という意味ではありません)。この機能を使って5つ値を取り出し、5日間の平均を求めるといった計算ができます。取り出す位置(下限と上限)を順に変えていけば、次々と移動平均が求められます。

 ということで、以下のコードの空欄になっている箇所(_____の部分)を埋めて完成させてください。

def movingaverage(data, period): # 移動平均の期間はperiodとする
  n = len(data)
  result = []
  for i in range(0, _____):
    result.append(round(average(data[_____]), 2))
  return result

movingaverage(data, 5)
# 出力例:[120.78, 120.25, 120.66, ... , 146.52]

リスト18 n日移動平均を求める関数movingaverageを完成させよう
仮引数のdataには株価のデータをリストとして与え、periodには移動平均の期間(nに当たる値)を渡すようにする

練習問題の解答例

 練習問題の解答例は以下の動画でも紹介しています。ぜひ参考にしてみてください。

動画5 練習問題の解答


(1)ネイピア数(e)の近似値を求める関数myexpの作成例

 これは数式をそのままプログラムとして表現しただけなので難しくないですね。

import math
def myexp(n):
  sum = 0
  for i in range(n):
    sum += 1 / math.factorial(i)
  return sum

リスト19 ネイピア数を求めるmyexp関数の作成例

(2)共分散を求める関数covarの作成例

 これも数式をそのままプログラムとして表現しただけです。制御変数iがそのままxyのインデックスになっているので簡単です。

def covar(x, y):
  n = len(x)
  avex = average(x)
  avey = average(y)
  sumxy = 0
  for i in range(0, n):
    sumxy += (x[i] - avex) * (y[i] - avey)
  return sumxy/n

リスト20 共分散を求める関数covarの作成例

 数式に従って素直にコードを書くと上のようになりますが、Pythonではfor文の部分を以下のように書いた方が簡潔です。

  for x_i, y_i in zip(x, y):
    sumxy += (x_i - avex) * (y_i - avey)

リスト21 zip関数を使って、for文を簡潔に書く
zip関数は複数の値をまとめ、繰り返しに使える値を返す関数。この例なら(x[0], y[0])に当たる値をそれぞれx_i, y_iに代入してfor文の中の文を実行し、次に(x[1], y[1])に当たる値をそれぞれx_i, y_iに代入してfor文の中の文を実行する、という具合に繰り返し処理が進められる。どちらか少ない方の要素がなくなったらzip関数が値を返さなくなるので、繰り返しが終わる。

(3)n日移動平均を求める関数movingaverageの作成例

 平均を求めるためには、リスト9のavarage関数を呼び出すだけなので簡単ですが、プログラムを完成させるには、制御変数とインデックスの対応を考える必要があります。

 例えば、5日移動平均であれば、繰り返しの0回目は0〜4番の平均を求めます。繰り返しの1回目は1〜5番の平均です。データの件数が100件あれば、最後のインデックスは99番なので、最後の繰り返しは95回目となり、95番〜99番の平均を求めます。

 従って、繰り返しは0回〜96回未満までとなります。n=100period=5なので、96はn-period+1で求められます。つまり、range(0, n-period+1)を指定すればいいですね。

 スライスについては、i番からi+period番未満を取り出せばいいので、data[i:i+period]とします。

def movingaverage(data, period): # 移動平均の期間はperiodとする
  n = len(data)
  result = []
  for i in range(0, n-period+1):
    result.append(round(average(data[i:i+period]), 2))
  return result

リスト22 n日移動平均を求める関数movingaverageの作成例

コラム 共分散から相関係数を求めるには

 共分散は元の値の大小によって、結果の値の範囲が大きく変わります。そこで、結果をそれぞれの標準偏差の積で割ると、値を-11の範囲に調整できます。その値が相関係数です。標準偏差は以下の式で求められます。

 この式を基に標準偏差を求める関数stdevpを作成し、相関係数を求めてみると、以下のようになります。

import math

def stdevp(x):
  n = len(x)
  avex = sum(x)/n
  sqsum = 0
  for i in range(0, n):
    sqsum += (x[i] - avex)**2
  return math.sqrt(sqsum/n)

covar(a, b)/(stdevp(a)*stdevp(b))
# 出力例:0.8633475827899139

リスト23 相関係数を求めるプログラム



 今回は、オイラーのγを求めるというトピックを通じて、数学に関しては数列や数列の和について見ました。そして、数式で表された総和などをプログラムとして表すことに取り組みました。また、繰り返しの制御変数とリストのインデックスを対応させる方法について、身近な例で考えてみました。

 前回、今回と計算が続いたので、次回はちょっと気分を変えて、図形やグラフの描画をテーマとし、数式や数式で表されるさまざまな現象を視覚化する方法を見ていきたいと思います。

「数学×Pythonプログラミング入門」のインデックス

数学×Pythonプログラミング入門

前のページへ 1|2       

Copyright© Digital Advantage Corp. All Rights Reserved.

アイティメディアからのお知らせ

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2026
人に頼れない今こそ、本音で語るセキュリティ「モダナイズ」
4AI by @IT - AIを作り、動かし、守り、生かす
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。