読者です 読者をやめる 読者になる 読者になる

StatsBeginner: 初学者の統計学習ノート

初学者が統計学、機械学習、R、Pythonの勉強の過程をメモっていくノート。

S字型カーブに沿って成長する皮算用をRで行う

R Tips

S字型皮算用の必要性

 サラリーマンなら誰しも、施策の将来効果を推計したりする際、あまり根拠となるデータもないので適当に鉛筆をなめるという局面は、ありまくると思います。そりゃ根拠あったほうがいいに決まってるんですが、データを集めるのにコストかかるし、難しい計算方法知らないし、てきとうに直線的に伸びていく数字を並べておくだけで意思決定は通って結果もそれなりに出たりするわけです。
 また、正式な投資の決定とかに使う資料はそれなりにしっかり作るとしても、ディスカッションレベルであれば短時間で手に入る数字をてきとうに組み合わせてあたりをつけるだけで、とりあえずいいやって思うことも多いでしょう。まじめな人はどうだかしりませんが。


 で、たとえば何かの成長の予測を、直線よりはS字カーブに沿うようにしといたほうがまだしもリアルっぽいなと思うことは多いですよね。べつに根拠なくても。
 ところが文系の人間にとっては、良い感じのS字型になるようにエクセルに数字を埋めていくとかってのは、どうやったらいいのかなかなか想像がつきません。


 そこで私は以前、S字型の曲線になる関数を何個か調べて、エクセルに関数として入れておき、S字型皮算用を容易に行えるようなシートをつくりました。
 S字型のカーブといってもいろいろあって、Wikipediaの「シグモイド」の項(リンク)をみると何個か載ってるのですが、個人的には「tanh」(ハイパボリックタンジェント)をつかったS字が、自分の思う「S字」の形状に近く、かつ作りやすいと思いました。
 (需要が飽和していく様子などは、ロジスティック曲線のほうが理論的に自然だと思いますが。)
 
 

S字型皮算用ツールをRでつくってみる

 このエントリでは、tanhをつかったS字型皮算用をRで行うための関数をつくってみたいと思います。
 Rでやる必然性はどこにもないです。エクセルで十分、というかエクセルでつくったほうがすぐに「皮算用資料」に落とし込めるので便利です。会社のPCにもRをインストールしてありますが、エクセルを使うと思います。
 でも今日は、Rで自作の関数をつくる練習のためにやってみます。
 

 自作の関数をつくるには、とにかく「function」の書式を覚えることですね。覚えるほどのことでもないですが。

 

関数名 <- function (引数のリスト) {処理内容}


 の形で書けばいいわけです。
 S字型皮算用を行うプロセスを考えると、
 
 

  • 何期分の成長曲線を描きたいのか決める(たとえば36ヵ月とか)
  • 最初の期の値を決める
  • 最後の期の値を決める(目指すべきところ)
  • カーブの湾曲具合を設定する


 といった程度のことができれば十分です。最初と最後の値を決めて、直線的にゴールに向かうのではなく「良い感じのS字」型に曲がってくれればいいわけです。グラフが。
 で、今回つくってみるツールは、そういった値を設定した上で、S字型に成長する数字の列を求め、

  • 美しいS字になってるか確認のためにグラフを描く
  • 数字の表をcsvで出力する


 というところまでやりたいと思います。
 csvで出すとき、数字がタテに並んでいるほうが良いか、横に並んでいるほうが良いかは、作ろうとしている皮算用資料のフォーマットにもよりますので、どちらか選択できるようにします。


 というわけでさっそく、自作関数を書いてみました。私は初学者なので、うまく書くとかいうことを考える余裕はなく、とりあえず欲しい結果が得られればOKとします。

※はてなブログにコードを書くと、半角スペースがけっこう消えてしまって見づらくなるので、全角スペースでインデントしています。


scurve <- function (
 from,      # スタート値
 to,        # ゴール値
 period,     # 期の数
 curve=2,    # カーブの曲がり具合(デフォルト値は2とする)
 direction="v",  # タテの表を得たければ"v"、ヨコなら"h"を設定することにする
 filename="scurve.csv"

 ) {  # ここまでが引数の設定で、ここから下が処理を表す。

 x1 <- seq(-curve, curve, length=c)
 x2 <- tanh(x1)+1
 x3 <- (x2-min(x2))/(max(x2)-min(x2))
 x <- seq(1:period)
 y <- from+((to-from)*x3)
 dir <- c(direction)
 
 if (dir[1]=="v") {
  table <- cbind(x, y)
  colnames(table) <- c("Period", "y")
  write.csv(table, filename)
  } else if (dir[1]=="h") {
  table <- rbind(x, y)
  rownames(table) <- c("Period", "y")
  write.csv(table, filename)
  } else {warning(message="テーブルは書き出してません。")} # 警告文の定義

plot(x, y, type="l", xlab="Period", xlim=c(1, period), ylim=c(from, to)) # 確認用グラフの描画
}


 これでOKです。関数の定義おわり。
 引数のリストを書くときに、「=○○」を付けておくと、これがデフォルト値になるらしいです。
 初心者がハマリやすい落とし穴としては、

  • 条件文で「○○が○○に一致する場合」を書くときは「=」を2個並べないとダメということ
  • if( ) { }のあとにelse { }を書きたいとして、ifの最後の「}」のあとに改行してしまうと構文が分離していると判定されるので、必ず「} else 」と続けて書くこと(これはRの独特な仕様らしい)

 ぐらいでしょうか。


 この定義済みの関数に値を入れるとどういう処理が行われるかというと、たとえば


 スタート値:売上1万個
 ゴール値:売上1億個
 期の数:36ヵ月(成長にかかる期間)
 カーブの強さ:2
 得たい数表の向き:タテ向き
 書き出すcsvのファイル名「scurve.csv」


 になるように設定して計算したとすると、以下のようになります。

  1. 最小-2、最大2で、等間隔にデータを36個並べたベクトルをつくる・・・{ x_1 }
  2. 上記ベクトルを「tanh」で変換し、1を足す(tanhは-1〜1の値を取るので)・・・{ x_2 }
  3. 1個目の値を0%、最後の値を100%として、100分率で表す形にする・・・{ x_3 }
  4. 期を表す、1〜36までのベクトルをつくる・・・{ x }
  5. スタート値で始まってゴール値で終わる、{ x_3 }の100分率にしたがって増えていく数字からなるベクトルをつくる(これが推計値となる)・・・{ y }
  6. 「方向」を「タテ(v)」として設定したならば、1列目に期、2列目に推計値となるようなテーブルをcsvで書き出し、「ヨコ(h)」設定ならば、1行目に期、2行目に推計値となるようなテーブルを書き出す。設定がなければ何も書き出さない。
  7. x軸を期、y軸を推計値とした、S字型のグラフを描く。

 
 

実行してみた結果

 では上の関数を実行してみます。

scurve(from=10000, to=100000000, period=36, curve=2, direction="v", filename="scurve_result1.csv")


 グラフはこんな感じになりました。なかなか立派なS字です。


 f:id:midnightseminar:20140728223010p:plain


 数表はこんな感じで出てきました。(長いので途中まで)


 f:id:midnightseminar:20140728223028p:plain:w350


 これを皮算用資料にはりつければOKですね。社長の目もごまかせます。
 ためしに設定を変えて、カーブの曲がり具合3、表はヨコ向きにしてみます。

scurve(from=10000, to=100000000, period=36, curve=3, direction="h", filename="scurve_result2.csv")


 こんな感じの結果になりました。


 f:id:midnightseminar:20140729002840p:plain


 f:id:midnightseminar:20140728223106p:plain:w350


 V1、V2、V3、、、というのは、どうやったら消えるのかわかりません。が、細かいことは気にしないこととします。


 ちなみにRの関数は、引数を指定するときに頭文字だけ(頭文字が同じ引数がある場合は当該引数については区別がつく文字数だけ)書いてればOKらしいです。
 つまり、

scurve(fr=500, t=3000, p=12, c=2.5, d="", fi="") # テーブルは出力しない


 これでいけるんです。


 f:id:midnightseminar:20140729005853p:plain


 定義した順番に並んでさえいれば、「fr=」とかそのものを省略しても大丈夫ですが。
 ちなみに、上記の入力内容の場合、数表の向き(d=)を指定していないので、csvは出力されずに自分で設定した警告文が出ます。

> scurve(fr=500, t=3000, p=12, c=2.5, d="", fi="") # テーブルは出力しない
Warning message:
In scurve(fr = 500, t = 3000, p = 12, c = 2.5, d = "", fi = "") :
テーブルは書き出してません。

>