ファイルからデータを逐次読み込んでいって、その中央値(メディアン)を計算したいのです。平均値だと
sum=0:ct=0
open "file.txt" for input as #1
do
input #1,dat
ct=ct+1
sum=sum+dat
loop until eof(1)
close #1
avg=sum/ct
でいけますが、メディアンはやはり配列変数に読み込んでソーティングしないと無理なのでしょうか?
できれば、配列変数を用いないで上のような形でできないものでしょうか?
大変お待たせ致しました。いやぁ、いざやってみると結構複雑でした。
Excelでサンプルデータを作ってテストしたので、多分間違っていないと思いますが、
確信は無いのでi_kumagoroさんの方法で計算した方が間違えがなくて確実かも。
前述の通り、平均値を求めて、そこから中央値に近付く方法で組んでみました。
件数が多ければ早く処理できると思うのですが...。
ちなみに偶数の場合は2つの平均というのが無ければ、もっと楽なんですけどね...。
自己満足で作りましたがご参考まで。
Function medianVB() Dim i As Integer 'カウンタ Dim ic As Integer '無限ループ対策 Dim av As Double '平均値 Dim ma As Double '最大値 Dim mi As Double '最小値 Dim cc As Integer '大きい値から数えた中央値の位置 Dim tp As Integer '件数が偶数なら1、奇数なら0 Dim mm As Double 'ターゲット値 Dim m1 As Double '中央値1 Dim lc As Integer '該当する個数 Dim sc As Integer '同じ値の個数 Dim rd As Integer 'データ読み込み用 Dim sr As Boolean Open "med.txt" For Input As #1 i = 0 av = 0 ma = 1000 mi = 0 '件数,平均値,最大値を求める Do Until EOF(1) Input #1, rd av = av + rd i = i + 1 Loop cc = i av = av / cc tp = (cc + 1) Mod 2 cc = Int((cc + 1) / 2) '平均値よりも大きい最小の値を探す mm = FindNextNum(av, sc, lc, ma, True) If cc <= lc And cc >= (lc - sc) Then If (tp = 0) Or (tp = 1 And ((cc + tp) <= lc)) Then '探した値が中央値の時 medianVB = mm Close Exit Function Else '探した値と次の値が中央値の時 m1 = mm mm = FindNextNum(m1, sc, lc, mi, False) medianVB = (m1 + mm) / 2 Close Exit Function End If Else If cc < lc Then sr = True Else sr = False ma = mi End If ic = 0 Do mm = FindNextNum(mm, sc, lc, ma, sr) If cc <= lc And cc >= (lc - sc) Then If (tp = 0) Or (tp = 1 And ((cc + tp) <= lc)) Then medianVB = mm Close Exit Function Else m1 = mm mm = FindNextNum(m1, sc, lc, mi, False) medianVB = (m1 + mm) / 2 Close Exit Function End If End If ic = ic + 1 If ic > cc Then Stop End If Loop End If End Function Function FindNextNum(tn As Double, sc As Integer, lc As Integer, ByVal mm As Integer, sr As Boolean) As Integer 'tn:基準値 'sc:FindNextNumと同値の個数-1 'lc:該当する個数 'mm:最大値/最小値 'sr:True=基準値よりも大きい数値を探す Dim rd As Integer Seek 1, 1 sc = 0 lc = 0 Do Until EOF(1) Input #1, rd If sr Then If tn < rd Then lc = lc + 1 If mm > rd Then mm = rd sc = 0 ElseIf mm = rd Then sc = sc + 1 End If End If Else If tn > rd Then If mm < rd Then mm = rd sc = 0 ElseIf mm = rd Then sc = sc + 1 End If Else lc = lc + 1 End If End If Loop If sr = False Then lc = lc + sc + 1 End If FindNextNum = mm End Function
下のページの下のほうに、
'配列の中央値を求める
というのがあります。
http://www.kms.ac.jp/~clinilab/person/ing/lib/static.bas
中央値の算出について、一般式が
http://aoki2.si.gunma-u.ac.jp/lecture/Univariate/median.html
にあります。
ありがとうございます。
データを配列変数に格納するなら、いろいろ手立てはありそうですね。ただ、ぐうたらなので、できれば、配列を使いたくない(^^;)
青木先生のページは僕も良く使わせていただいています。
すみません!先ほどのプログラムをExcelで検証してみましたが、ソートされていることが前提になっていて、うまく動作しません。
Excelならmedian関数があるのですが、これも多分内部でソートして計算しているのだと思います。
お力になれずすみませんでした。
そうですね。配列のちょうど真ん中を選び出すプログラムみたいでした。
でも、ありがとうございます。m(_ _)m
要素数がメモリを逼迫するほど多いとかでなければお勧めはしませんが(そうであっても大分改良が必要だと思います)、
でできなくはないです。
例えば
1, 3, 4, 5, 6, 6, 7, 8, 10, 11, 11, 13
の様なデータがあったとすれば(ソートされている必要はありません)
の様になります。読み込みループでは現在の順位と前回読み込みの最大値を憶えておけばよいので配列はいりません。
VBのコードは書けないのでだらだらとした説明になってすみません。
反復横とび、じゃない、反復読み込みで中央部を見つける方法ですね。力技ですね。こういうやり方もあるのか、と勉強になります。
ありがとうございます。
最大値と最小値のチェックをうまくやっていけば、反復横とび(じゃないって)をしなくても中央にたどり着けるのかもしれないですね...
配列を使う方が明らかに効率的ですが、パズルとして楽しんで解法を探っています。
色々考えましたが、反復横飛び(じゃない!)反復読み込みになるでしょうね。
私は少しでも効率的に...と思って、平均値から探す方法を考えています。
が、考えると結構複雑でして(楽しいから全然良いのだけど)、出来たら閉じずに
もう少し待って貰えると助かります。(ただいま作成中です)
お待ちしてます(^^)
いつもじゃまくさいので平均値を使っているのですが、データにばらつきが多いとメディアンはどうなってるんだろうと気になりつつもほったらかしてます。
大変お待たせ致しました。いやぁ、いざやってみると結構複雑でした。
Excelでサンプルデータを作ってテストしたので、多分間違っていないと思いますが、
確信は無いのでi_kumagoroさんの方法で計算した方が間違えがなくて確実かも。
前述の通り、平均値を求めて、そこから中央値に近付く方法で組んでみました。
件数が多ければ早く処理できると思うのですが...。
ちなみに偶数の場合は2つの平均というのが無ければ、もっと楽なんですけどね...。
自己満足で作りましたがご参考まで。
Function medianVB() Dim i As Integer 'カウンタ Dim ic As Integer '無限ループ対策 Dim av As Double '平均値 Dim ma As Double '最大値 Dim mi As Double '最小値 Dim cc As Integer '大きい値から数えた中央値の位置 Dim tp As Integer '件数が偶数なら1、奇数なら0 Dim mm As Double 'ターゲット値 Dim m1 As Double '中央値1 Dim lc As Integer '該当する個数 Dim sc As Integer '同じ値の個数 Dim rd As Integer 'データ読み込み用 Dim sr As Boolean Open "med.txt" For Input As #1 i = 0 av = 0 ma = 1000 mi = 0 '件数,平均値,最大値を求める Do Until EOF(1) Input #1, rd av = av + rd i = i + 1 Loop cc = i av = av / cc tp = (cc + 1) Mod 2 cc = Int((cc + 1) / 2) '平均値よりも大きい最小の値を探す mm = FindNextNum(av, sc, lc, ma, True) If cc <= lc And cc >= (lc - sc) Then If (tp = 0) Or (tp = 1 And ((cc + tp) <= lc)) Then '探した値が中央値の時 medianVB = mm Close Exit Function Else '探した値と次の値が中央値の時 m1 = mm mm = FindNextNum(m1, sc, lc, mi, False) medianVB = (m1 + mm) / 2 Close Exit Function End If Else If cc < lc Then sr = True Else sr = False ma = mi End If ic = 0 Do mm = FindNextNum(mm, sc, lc, ma, sr) If cc <= lc And cc >= (lc - sc) Then If (tp = 0) Or (tp = 1 And ((cc + tp) <= lc)) Then medianVB = mm Close Exit Function Else m1 = mm mm = FindNextNum(m1, sc, lc, mi, False) medianVB = (m1 + mm) / 2 Close Exit Function End If End If ic = ic + 1 If ic > cc Then Stop End If Loop End If End Function Function FindNextNum(tn As Double, sc As Integer, lc As Integer, ByVal mm As Integer, sr As Boolean) As Integer 'tn:基準値 'sc:FindNextNumと同値の個数-1 'lc:該当する個数 'mm:最大値/最小値 'sr:True=基準値よりも大きい数値を探す Dim rd As Integer Seek 1, 1 sc = 0 lc = 0 Do Until EOF(1) Input #1, rd If sr Then If tn < rd Then lc = lc + 1 If mm > rd Then mm = rd sc = 0 ElseIf mm = rd Then sc = sc + 1 End If End If Else If tn > rd Then If mm < rd Then mm = rd sc = 0 ElseIf mm = rd Then sc = sc + 1 End If Else lc = lc + 1 End If End If Loop If sr = False Then lc = lc + sc + 1 End If FindNextNum = mm End Function
すばらしいです!
試してみますね。
ありがとうございます!
すばらしいです!
試してみますね。
ありがとうございます!