データ分析
人工知能
統計学
RxSwift
swift4


swiftで統計的データを扱う

プログラミングを用い統計を扱うとき、大抵はpythonやRなどのライブラリが豊富なものを取り入れると思います。そしてそれは多くの場合で最適な選択かと思います。しかし今回はその流れに抗って敢えてネイティブのswiftでいけるところまでいってみます。

なお、今回はstoryboradは使わず、rxswiftの勉強も兼ねて入力に対して動的に統計データを計算表示するプチアプリの作成を最終目標にします。


まずシンプルに統計の公式を作成

使いやすい(使う用途は不明)ようにArrayのextensionとして作成してみます。ちなみにfloatよりdoubleのほうが精度が高いらしいです。(ググると適当にヒットします。)

今回は、配列なのを利用してreduce,mapをじゃんじゃん駆使してみます。(初めて使ったのですが、慣れると楽ですね。)

なお、今回は以下の仮定のもと作成しております。


  • 対象データは不偏標本と仮定。(これは、主に分散の分母がnかn-1かという点に関わります。n-1とかの理由としては、母集団の真分散の期待値に不偏標本分散の期待値を等しくさせるための作為的なものです。あとで説明します。)

  • データが二個以上ないと死にます。(普通にエラー吐かれます。)

  • 計算処理の考慮はほぼしてない。(今後気が向いたらします。)

とりあえずは対象集合は1つで。

extension Array where Element == Double{

//総和
//全体の各要素を足し合わせるだけ。
func sum(_ array:[Double])->Double{
return array.reduce(0,+)
}
//平均値
//全体の総和をその個数で割る。
func average(_ array:[Double])->Double{
return self.sum(array) / Double(array.count)
}
//分散
//求め方が2つあるが、計算量が少ないと思われる方法を採用。
//V=E[X^2]-E[X]^2が成立。
func variance(_ array:[Double])->Double{
let left=self.average(array.map{pow($0, 2.0)})
let right=pow(self.average(array), 2.0)
let count=array.count
return (left-right)*Double(count/(count-1))
}
//標準偏差
//これは分散の正の平方根。つまりS=√V
func standardDeviation(_ array:[Double]) -> Double {
return sqrt(self.variance(array))
}
//偏差
//各要素と全体の平均値の差
//後の標準化で活躍するため求めとく。
func deviation(_ array:[Double]) -> [Double] {
let average = self.average(array)
return array.map{ $0 - average }
}
//分散もう1つのパターン
/*
func variance(_ array:[Double])->Double{
return self.average(pow(self.deviation(array),2.0))
}
*/

//歪度
func skewness(_ array:[Double])->Double{
return self.average(self.deviation(array).map{pow($0, 3.0)}) / pow(self.standardDeviation(array), 3.0)
}
//尖度
func kurtosis(_ array:[Double])->Double{
return (self.average(self.deviation(array).map{pow($0, 4.0)}) / pow(self.standardDeviation(array), 4.0) - 3.0)// 正規分布を想定
}
//幾何平均
func geometricMean(_ array:[Double])->Double{
return pow(array.reduce(0, *), 1.0 / Double(array.count))
//この方法だと、nが大きい場合に計算制度が著しく落ちる。というか計算できない。
}
//調和平均
func harmonicMean(_ array:[Double])->Double{
let left=Double(array.count)
let right=self.sum(array.map{1 / $0})
return left/right
}
//中央値
func median(_ array:[Double])->Double{
//indexとの差を-1することで消す。
let count=array.count-1
print("count:"+String(count))
print(array[Int(floor(Double(count/2)))])
if count%2==0{
return array[Int(count/2)]
}else{
return ((array[Int(floor(Double(count/2)))]+array[Int(floor(Double(count/2)))+1]) / 2)
}
}
//最頻値
func mode(_ array:[Double])->[Double]{
var sameList:[Double]=[]
var countList:[Int]=[]
for item in array{
if let index=sameList.firstIndex(of: item){
countList[index] += 1
}else{
sameList.append(item)
countList.append(1)
}
}
let maxCount=countList.max(by: {$1 > $0})
var modeList:[Double]=[]
for index in 0..<countList.count{
if countList[index]==maxCount!{
modeList.append(sameList[index])
}
}
return modeList
}
//変動係数
//割るだけなので登場させました。
func coefficientOfVariation(_ array:[Double])->Double{
return self.standardDeviation(array)/self.average(array)
}
}


使用したメソッドとかの説明

reduce, map, pow, sqrtくらいは説明しときます。


reduce, mapとかいう優れものたちの説明

reduce, mapあたり(filterとかも)は、説明してもほんとややこしくなるだけなので、今回の使用用途に限定して説明します。(全体的な説明が欲しい方はこちらなどが参考になります。)

reduceは、全要素を1つの要素にすることに使用しています。具体的には、sumの部分で1つ1つの要素を足し合わせています。第一引数にはどの部分の要素か(今回は各要素1つずつ)を、第二引数にはどの四則演算式を用いるか(今回は加算)を与えています。

func sum(_ array:[Double])->Double{

return array.reduce(0,+)
}

mapは、全要素に対して何か演算処理を施しその返り値を新しく生成します。簡単に言ったら与えた配列は進化して戻ってきます。例えば、偏差(偏差=(要素)ー(平均))の部分で、平均値という定数を全要素に対してマイナスしてあげてます。

func deviation(_ array:[Double]) -> [Double] {

let average = self.average(array)
return array.map{ $0 - average }
}


pow, sqrtの説明

powは累乗です。すなわち、pow(a,b)とは、aのb乗になります。数式で表現すると、a^bですね。

sqrtは逆に、平方根になります。そもそも、pow(a,1/b)ということも可能なので、存在意義はあるのですかね?(ご存知の方教えて頂けましたら幸いでございます。)


一応統計的説明も

趣旨が逸れてしまいそうで言及してなかったのですが、統計的な観点の説明もしておきます。


なぜ分散の分母がn-1なのか。 nではなくn-1とすべき理由

そもそも分散と一括りに言いましても、母分散と標本分散という2つが主に登場してきます。簡単に説明しておきますと、母分散が宇宙数多ある無限のデータの真の分散の値です。そのためその値を知ることはできません。しかし、それでは統計や予測をする際に問題が生じます。そのため昔の頭の良い学者さんは考えました。その真の値に限りなく近いものを求めてしまえば良いのだ!と。はい、お待ちかねです。ここで登場するのが標本分散という分散になります。

すなわち、標本分散というのは、ほぼ母分散であり、むしろそれ以外ではいけないのです。

なぜn-1という値が最も適切な値なのかは、完全数学の話なのでググってみてください。(例えばこことかは非常にわかりやすいです。)


偏差とは何か。

偏差はデータ分析をする際にいいパラメータになります。(感覚的にもすごくわかりやすいものです。)

とはいえ、その実態はよくわからない方も多いと思うので一応説明しておきます。偏差は、偏差値という言葉があるように、単位とかデータの性質(いわば癖ですね)にとらわれず、いい感じに平等にデータを比較することに長けてると考えられています。

具体的にいうと、算数の点数で算数の偏差値が出るとして、体育実技の点数でも体育実技の偏差値が個別に出るとします。A君は算数で偏差値80でした。B君は体育実技で偏差値75でした。どっちの方がすごいか。偏差値を見れば明らかですね。A君です。

こんな感じのノリです。本来なら比較することが難しい複数データをデータ特有の性質を削ぎ落とすことで比較することを可能にしていますね。


一旦ここまでで

疲れました。一旦ここまでで。次は、rxswiftのコラボレーションを目指します。(ちなみに僕はrxswiftを触ったことがありません。デビューさせてください。)