2012年01月10日

 統計学復習メモ19: 分散分析の種類

分散分析というと、その名前自体によく「一元配置」「二元配置」とか「対応あり」「対応なし」とか「繰り返しのある」とか「繰り返しのない」とかいう言葉がついて回る。統計学の書籍でも、「一元配置分散分析」と「二元配置分散分析」は項目を分けて説明されることが多い。さらにそれぞれが「対応あり/なし」「繰り返しあり/なし」等で分けられると、目次に「分散分析」がたくさん並ぶことになる。Excelの「分析ツール」のメニューにも分散分析の項目があるが、

  • 分散分析: 一元配置
  • 分散分析: 繰り返しのある二元配置
  • 分散分析: 繰り返しのない二元配置
などと分かれており、試しにやってみようと思っても、それらの違いがわからないとどうしようもない。同じ分散分析でも、それらの選択によっては、結果が的外れな意味を為さないものになる可能性があるのだ。その「分散分析」に付加される言葉の種類の多さに圧倒されて、分散分析は覚えることが多いと思って勉強する気を失ったのは、筆者だけであろうか。

筆者は未だに、どういう時にどの種類の分散分析を使うべきなのかよくわかっていない。 頭の中を整理するために、いつものように体当たり的に、

  • 一元配置(対応なし)
  • 一元配置(対応あり)
  • 二元配置(繰り返しなし)
  • 二元配置(繰り返しあり、対応なし)
  • 二元配置(繰り返しあり、1要因対応あり)
  • 二元配置(繰り返しあり、2要因対応あり)
のそれぞれの適用例を、筆者の職業に関係のあるソフトウェア開発を題材にして、考えてみることにした。

以下のデータは、全て架空のものである。
筆者はExcelを持っていないので、分散分析の計算にはRを使う。Rにも分散分析の関数はoneway.test(), aov(), anova(), lme()など色々あるが、今回は上記の全てをカバーできるaov()を使う。


最もシンプルな、一元配置(対応なし)の例として、次のようなデータを考える。

一元配置(対応なし)の例(1)
要因:モジュールの階層
アプリミドルドライバ
生産性
(steps/人月)
490
410
590
500
460
690
750
500
770
720
730
480
650
310
450
530
550
350
460
370
610
240
500
この生産性はモジュールの階層によって差があると言えるかどうか(差が無いなら滅多にこうはならないこと)を、分散分析で調べる。
> sample1 <- read.table("anova01.dat", header=TRUE)
> summary(aov(productivity ~ module, data=sample1))
            Df Sum Sq Mean Sq F value  Pr(>F)  
module       2 120939   60470  3.5656 0.04738 *
Residuals   20 339182   16959                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
> 
"Df"が自由度、"module"の行の"Sum Sq"が群間平方和(ここではモジュールの階層の違いによる効果)、"Mean Sq"が群間の不偏分散(モジュールの階層の違いによるばらつきの分散値)、Residuals(残差=群内のばらつき=全体に共通するはずのばらつき)の行のMean Sqが群内(全群共通)の不偏分散、"F value"が(群間/群内)の分散比、"Pr"(P値、そのF値以上が起こる確率)の右の'*'が有意水準0.05で有意であることを表す。
分散分析の帰無仮説は「全ての標本は同じ分布に従う母集団から得られたもの」→「各群の分布が同じであること」→「群間にばらつきがないこと」なので、分散比が有意であれば、「全体のばらつきに対して群間にばらつきがないとは言えない」、細かいことを言わなければ、群間にばらつきが認められ、各群の分布は同じでないことになる。
この例では、生産性はモジュールの階層によって違いがありそうということになる。

次の例は、同じ観測値を別の要因(観点)で分けたものである。

一元配置(対応なし)の例(2)
要因:所属
A社B社C社
生産性
(steps/人月)
490
410
460
590
500
370
460
690
750
480
500
650
770
610
310
720
450
530
240
550
350
730
500
> summary(aov(productivity ~ company, data=sample1))
            Df Sum Sq Mean Sq F value Pr(>F)
company      2  68444   34222  1.7475 0.1998
Residuals   20 391677   19584               
>
測定値が同じであることを明確にする為に、データファイルを同じsample1にしている。'*'のマークとその説明が出力されていないので、所属別では群間に有意な差が無く、このデータでは所属によって生産性にばらつきがあるとは言えないことになる。

もう1つ例を考えた。

一元配置(対応なし)の例(3)
要因:仕様書のファイル形式
.doc.txt.xls.ppt
システムテスト
不具合数(/kstep)
3
10
8
2
2
5
4
4
6
0
11
11
7
1
3
7
5
10
4
10
11
12
5
10
14
11
> sample2 <- read.table("anova02.dat", header=TRUE)
> summary(aov(error ~ format, data=sample2))
            Df Sum Sq Mean Sq F value  Pr(>F)  
format       3 113.58  37.861  3.1192 0.04672 *
Residuals   22 267.03  12.138                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
このデータでは、システムテストで見つかったバグ数は、仕様書のファイル形式によって違いがありそうということになる。

測定データを一元配置分散分析(対応なし)する時のポイントを整理する。

  • 測定値は正規分布することを仮定できるものであること
  • 差があることを調べる群は、3群以上あること (2群しか無いなら、その差をt検定するのと変わらない)
    つまり、要因(群の分け方)はその水準(標本が属する群を決めるもの、値やID)が3つ以上あるように選ぶこと
  • 各水準のデータ数は同じでなくても良い
  • 水準はなるべく定量的な数値でないこと、数値であれば、なるべく測定値の従属変数でないことが明確であること
    (要因と測定値に明確な関係があっても、群間のばらつきが十分に大きくないと有意差が検出できないため。また、水準(値の範囲)の取り方によって結果が変わってしまうため。例えば測定値と線形の関係にあるなら、無相関の検定をする方が良い)
最後の1点は、この記事を書くにあたって色々な例を考えてみた上での筆者の感想であり、そういうことを何かで読んだ記憶も無く、必ずしもそうではないかも知れない(温度の範囲などを水準に取る例も見かける)。


一元配置(対応あり)の例として、次のようなデータを考える。
各開発者がその4種類の開発プロセス(作業手順)を経験した時の、それぞれの開発プロセスにおける成績が、次のように得られたとする。

一元配置(対応あり)の例(1)
生産性
(steps/人月)
要因:開発プロセス
WaterfallSpiralIncrementalTDD




開発者A490750450610
開発者B410480530780
開発者C460500240680
開発者D590650550880
開発者E500770350520
開発者F370610730600
開発者G460310500400
開発者H6907206401030
Waterfallは要求分析、設計、実装、テストをこの順で1回ずつ行うやり方、Spiralは設計以降の作業を何回かに分けて設計、実装、テストを繰り返して積み上げ式に開発する方式、Incrementalは要求分析も含めて全体を繰り返す、1サイクル毎に一通り動くものができる機能追加方式、TDDは要求分析や設計をテストプログラム作成に代え、以降の作業はそのプログラムがOKを返すことだけを目的に行う、結果良ければ全て良し方式のことである。
開発者の能力や向き不向きには個人差があることを前提とし、それを計算に入れて、開発プロセスは生産性に影響するかどうかを、分散分析で調べる。
> sample3 <- read.table("anova03.dat", header=TRUE)
> summary(aov(productivity ~ process + Error(developer), data=sample3))

Error: developer
          Df Sum Sq Mean Sq F value Pr(>F)
Residuals  7 337872   48267               

Error: Within
          Df Sum Sq Mean Sq F value  Pr(>F)  
process    3 201184   67061  3.8348 0.02464 *
Residuals 21 367241   17488                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
"Error: developer"の部分は開発者の個人差による(開発者の違いを誤差要因とする)ばらつきに関するものであり、以降の計算はそれが除外されていることを示す。"Error: Within"の部分が、群内変動を誤差と見なして群間変動を検定するものである。
このデータでは、個人差の影響を差し引くと、開発プロセスは生産性に影響する要因と言えることになる。
もし、同じデータを「対応なし」として計算する(開発者の個人差を差し引かない)と、次のように、開発プロセス間に同じ有意差が出ない。
> summary(aov(productivity ~ process, data=sample3))
            Df Sum Sq Mean Sq F value  Pr(>F)  
process      3 201184   67061   2.663 0.06729 .
Residuals   28 705112   25183                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
実際には各開発プロセスを短期間に経験することは難しく、開発者の経年変化や学習効果もあるので、こういうデータは取りにくいが、例えば開発者がソフトハウスのことであり、ソフトハウス内ではばらつきが無いと仮定すれば、各プロセスを同時に行うことも可能であるし、何より、筆者がこれ以上単純明快な例を思い付かないので、これで良しとする。

次の例は、会社による差(観測対象の個体差)はあるものとして、業務分野の違いが生産性に影響すると言えるかどうかを調べる。

一元配置(対応あり)の例(2)
生産性
(steps/人月)
要因:開発分野
組み込み汎用業務システムゲーム




M社330470450320
N社290440370630
O社450550580750
P社320360470460
Q社370320430480
> sample4 <- read.table("anova04.dat", header=TRUE)
> summary(aov(performance ~ product + Error(company), data=sample4))

Error: company
          Df Sum Sq Mean Sq F value Pr(>F)
Residuals  4 102420   25605               

Error: Within
          Df Sum Sq Mean Sq F value  Pr(>F)  
product    3  80080 26693.3  4.0332 0.03379 *
Residuals 12  79420  6618.3                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
summary(aov(performance ~ product, data=sample4))
            Df Sum Sq Mean Sq F value Pr(>F)
product      3  80080   26693  2.3487 0.1111
Residuals   16 181840   11365               
1つ目のaov()が会社間に「対応あり」として計算した例、2つ目のaov()が「対応なし」として計算した例である。このデータだと、会社間の個体差を考慮に入れると、開発分野は生産性に影響する要因だったと言えることになる。

測定データを一元配置(対応あり)にする時のポイントを整理する。

  • 群内の各測定値が、どの個体から得られた標本であるか(または、誤差要因のどの水準に属する標本であるか)がわかること
  • 個体差によるばらつきはあるものとし、差し引いて考えるものであること
  • 個体に有意差があるかどうかを調べる必要は無いこと
  • 一部のデータが抜けていても計算可能
  • 同水準、同個体のデータは1つでなく複数あっても計算可能(但し全てのセルのデータ数が同じであることが望ましい)
つまり、「対応あり」とは、異なる水準間(上の表の列間)でどれとどれが同じ個体から採取された(一般化すると、同じ誤差要因を持つ)標本であるかの対応が取れることである。


二元配置(繰り返しなし)の例として、次のようなデータを考える。

二元配置(繰り返しなし)の例(1)
生産性
(steps/人月)
要因1:開発プロセス
WaterfallSpiralIncrementalTDD
要因2:
座席の形態
大部屋490700450560
自由席410430530730
パーティション460450240630
小部屋590720550830
自宅500600350470
開発プロセスの違いも作業空間(座席の形態)の違いも生産性に影響し、それらの影響が足し合わせれていると仮定して、それぞれの組み合わせについて1つずつデータを採取し、2要因について同時に、その仮定が正しそうかどうかを調べる。
> sample5 <- read.table("anova05.dat", header=TRUE)
> summary(aov(productivity ~ process + workspace, data=sample5))
            Df Sum Sq Mean Sq F value  Pr(>F)  
process      3 141255   47085  4.8533 0.01951 *
workspace    4 121420   30355  3.1288 0.05589 .
Residuals   12 116420    9702                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
よって、このデータでは、開発プロセスの違いによる効果は有意水準0.05で有意差あり、作業空間の違いによる効果は有意差なし(有意水準が0.1だと有意差あり)となる。
ちなみに、開発プロセスと作業空間についてそれぞれ、同じデータを一元配置とみなして計算すると、次のように、同じ水準の有意差は出ない。
> summary(aov(productivity ~ process, data=sample5))
            Df Sum Sq Mean Sq F value  Pr(>F)  
process      3 141255   47085  3.1675 0.05318 .
Residuals   16 237840   14865                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
> summary(aov(productivity ~ workspace, data=sample5))
            Df Sum Sq Mean Sq F value Pr(>F)
workspace    4 121420   30355  1.7671  0.188
Residuals   15 257675   17178               
このデータの特徴は、全体のばらつきを、2つの要因のばらつきと共通のばらつきの3つに分解するからこそ、明確になるのである。

次の例は、何らかの開発成果物の欠陥数を、作業期間と納入した曜日との組み合わせのそれぞれについて1つずつ、データを採取したとするものである。

二元配置(繰り返しなし)の例(2)
欠陥数
/kstep
要因1:開発期間
1週間2週間1ヶ月
要因2:
曜日
月曜1064
火曜696
水曜864
木曜494
金曜864
土曜1099
日曜N/A69
> sample6 <- read.table("anova06.dat", header=TRUE)
> summary(aov(error ~ time_limit + delivery, data=sample6))
            Df Sum Sq Mean Sq F value  Pr(>F)  
time_limit   2 14.360  7.1798  2.8898 0.09803 .
delivery     6 48.861  8.1435  3.2777 0.04224 *
Residuals   11 27.329  2.4845                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
1 observation deleted due to missingness
> summary(aov(error ~ delivery, data=sample6))
            Df Sum Sq Mean Sq F value  Pr(>F)  
delivery     6 51.217  8.5361  2.8213 0.05523 .
Residuals   13 39.333  3.0256                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
1 observation deleted due to missingness
よって、このデータでは、開発期間の違いによる差も考慮すると、納品日の曜日によって有意水準0.05で有意な差があるという結果になる。
2つ目のaov()は、上の例と同様、一元配置では同じ有意差が出ないことを確認したものである。

二元配置(繰り返しなし)にする時のポイントを整理する。

  • 2つの要因の全ての水準の組み合わせについて、データは1つ
  • どちらの要因も何らか影響していると思われ、それぞれの影響を分離して調べようとしていること
  • 一部のデータが抜けていても計算可能
  • 一元配置(対応あり、繰り返しなし)の代用として行うことも可能
表の形が同じことからもわかるように、一元配置(対応あり)と二元配置は計算方法が同じであり、一元配置の対応を決める要因についてのF検定を省くかどうかが異なるだけである。
Excelの分析ツールに「対応のある一元配置」が無いのは、二元配置で代用できるからだろう。


二元配置(繰り返しあり、対応なし)は、表の形としては、繰り返しのない二元配置の各セルに複数のデータが含まれるだけの違いなので、同じような要因のペアが使える。

二元配置(繰り返しあり、対応なし)の例(1)
生産性
(steps/人月)
要因1:開発プロセス
WaterfallSpiralIncrementalTDD
要因2:
座席の形態
大部屋 570
660
450
710
480
570
570
450
620
520
540
740
自由席 570
500
270
590
460
670
510
450
410
620
350
350
パーティション 300
480
580
670
500
710
610
540
540
430
650
600
小部屋 510
530
530
660
620
510
310
550
780
760
400
590
自宅 510
560
530
570
750
590
400
380
520
600
610
580
繰り返しがある(同じセルに複数のデータがある)と、行の効果、列の効果に加えて、交互作用の効果を分離して計算することが可能で、交互作用を分けるか分けないかで計算結果が変わる。
交互作用とは、2要因の水準の特定の組み合わせにだけに影響する効果のことで、例えばその行とその列は大体高い値なのにそのセルだけやたら低い値が多いということを起こす要因である。
aov()で交互作用を分離して分散分析するには、引数において2つの要因を'*'で繋ぐ。
> sample7 <- read.table("anova07.dat", header=TRUE)
> summary(aov(productivity ~ process * workspace, data=sample7));
                  Df Sum Sq Mean Sq F value  Pr(>F)  
process            3  98952   32984  2.4676 0.07599 .
workspace          4  65823   16456  1.2311 0.31308  
process:workspace 12  69657    5805  0.4343 0.93963  
Residuals         40 534667   13367                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
"process:workspace"の行が交互作用に関する行で、このデータでは有意差は出なかった。また、交互作用を分離すると、開発プロセスにも作業空間にも有意水準0.05の有意差はなしである。
交互作用を分離せずに計算するには、aov()の引数において、2つの要因を'+'で繋ぐ。
> summary(aov(productivity ~ process + workspace, data=sample7));
            Df Sum Sq Mean Sq F value  Pr(>F)  
process      3  98952   32984  2.8382 0.04686 *
workspace    4  65823   16456  1.4160 0.24170  
Residuals   52 604323   11622                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
このデータでは、交互作用を分離しなければ、開発プロセスには有意差が認められる(交互作用の効果を分離しない方が有意差が出る)ことがわかる。
ちなみに、繰り返しがないと、'+'でも'*'でも結果は変わらない。

もう1つ例を作る。

二元配置(繰り返しあり、対応なし)の例(2)
結合テストエラー数
/kstep
要因1:仕様書の配布形態
WordExcelPDFHTML
要因2:
設計書の
フォーマット
Word 8
6
2
1
7
5
7
3
9
10
4
11
5
6
3
11
7
14
7
9
Excel 11
5
8
13
8
4
10
6
3
6
5
9
13
6
10
17
12
11
5
10
Text 5
6
6
10
11
6
12
7
9
8
5
12
8
9
6
9
8
13
11
3
HTML 12
14
7
13
12
10
15
6
13
13
12
7
5
13
7
4
10
3
8
9
> sample8 <- read.table("anova08.dat", header=TRUE)
> summary(aov(error ~ given_spec * design_desc, data=sample8))
                       Df Sum Sq Mean Sq F value  Pr(>F)  
given_spec              3   86.5 28.8333  2.9254 0.04043 *
design_desc             3   17.1  5.7000  0.5783 0.63137  
given_spec:design_desc  9  198.4 22.0444  2.2366 0.03056 *
Residuals              64  630.8  9.8562                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
> summary(aov(error ~ given_spec + design_desc, data=sample8))
            Df Sum Sq Mean Sq F value  Pr(>F)  
given_spec   3   86.5  28.833  2.5384 0.06314 .
design_desc  3   17.1   5.700  0.5018 0.68221  
Residuals   73  829.2  11.359                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
1つ目のaov()の結果より、このデータでは仕様書のフォーマットが欠陥数に影響しており、また設計書のフォーマットとの交互作用もある結果となった。 2つ目のaov()の結果は、交互作用を分離しないと、仕様書のフォーマットについても同じ有意差は出ない(1つ前のデータ例とは逆に、交互作用を分離する方が有意差が出る)ことを示している。

二元配置(繰り返しあり、対応なし)にする時のポイントを整理する。

  • 2つの要因の全ての水準の組み合わせについて、複数のデータがある
  • それにより、2つの要因の交互作用が混入する
  • 交互作用によるばらつきを分離して計算するかどうかは場合による
  • 一元配置(対応あり、繰り返しあり)の代用として行うことも可能
一元配置(対応あり)を代用する時に交互作用をどう扱うかが問題になるが、誤差要因と何かの交互作用というのはやはり誤差要因だと考えるなら、交互作用を分離して、誤差要因も交互作用も検定から除外する(aov()なら'*'を使った上で検定中の要因そのもの以外の行を無視する)のが好ましいと思う。


「二元配置(1要因対応あり)」は、「対応のある要因と対応のない要因の二元配置」と書かれることが多いようだが、そう書かれると余計にややこしく見えるのは、筆者だけだろうか。
「対応あり」とは、一元配置の場合と同様、繰り返しのある(セル内に複数ある)データのそれぞれが、他の水準(セル)のどのデータに対応するかがわかる、という意味であり、対応を取るからには、個体差の影響を取り除いて計算する必要があると考えていることになる。
1要因についてのみ対応が取れる状況として、次のような例を考える。

二元配置(繰り返しあり、1要因対応あり)の例(1)
生産性
(steps/人月)
要因1:開発プロセス
WaterfallSpiralIncrementalTDD
要因2:
座席の形態
+ 人
作業場開発者
大部屋 A630660570340
B530430430400
C390640520530
パーティション D400760500680
E550670290890
F650620280820
小部屋 G470680760410
H530580670930
I850980790980
自宅 J610850530310
K400640230400
L530430470330
開発者間には能力の個人差があるのは普通だから、開発者の違いは、標本の対応を取ってその影響を除外して計算するにふさわしい、誤差要因であろう。
前記のように同じ人が4つの開発プロセスを経験するというのは現実的に多少無理があるが、それはなされたとする。
全ての開発者がこれらの全ての作業空間を経験するのは現実的に不可能であろう。と思うので、そういう席替えはなされず、作業場毎に別の人を選んでデータを採取したとする。
つまり、開発プロセスについては、水準間でどれとどれが同じ人のデータであるかの対応があり、作業空間に関しては、水準間でそういう対応がない、というデータである。
> sample9 <- read.table("anova09.dat", header=TRUE)
> summary(aov(productivity ~ process * workspace + Error(developer), data=sample9))

Error: developer
          Df Sum Sq Mean Sq F value  Pr(>F)  
workspace  3 424492  141497  3.8251 0.05734 .
Residuals  8 295933   36992                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Error: Within
                  Df Sum Sq Mean Sq F value  Pr(>F)  
process            3 163692   54564  3.0262 0.04914 *
process:workspace  9 391275   43475  2.4112 0.04123 *
Residuals         24 432733   18031                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
このように、開発プロセスの違いによる影響には有意差があり、また開発プロセスと作業空間の間には何らかの交互作用があるという結果になった。
もし同じデータを対応なしの二元配置として計算すると、次のように全く異なる結果になる。
> summary(aov(productivity ~ process * workspace, data=sample9))
                  Df Sum Sq Mean Sq F value   Pr(>F)   
process            3 163692   54564  2.3962 0.086440 . 
workspace          3 424492  141497  6.2140 0.001898 **
process:workspace  9 391275   43475  1.9092 0.086483 . 
Residuals         32 728667   22771                    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 
これは、データをよく見るとわかるのだが、小部屋に飛び抜けて成績が良い人が居ることに起因しており、作業空間の違いによる効果だと言うにはかなり不適切であろう。

1要因にのみ対応がある例としてもう1つ、次のようなデータを考える。
開発者A〜Fで構成されるグループが、6つの分野の業務を行った時の生産性を測ったという例である。

二元配置(繰り返しあり、1要因対応あり)の例(2)
生産性
(steps/人月)
要因1:性別 + 人
性別男性女性
開発者ABCDEF
要因2:
業務分野
組み込み(アプリ) 470720550610580550
業務システム 490530530570310380
生産システム 530520570600520490
PCアプリ 530310460420690650
Javaアプリ 600530500450790590
ゲーム機ソフト 450530400530350530
上の例では対応を決める要因を縦に並べたが、今度は横に並べた。
開発者の能力には当然個人差がある。 全開発者が6つの分野にて仕事をしたので、分野間ではどれとどれが同じ人のデータであるかの対応が取れる。
1人の開発者は男性と女性の両方を経験できないので、男性群と女性群のデータには対応が取れない。
> sample10 <- read.table("anova10.dat", header=TRUE)
> summary(aov(productivity ~ sex * area - Error(developer), data=sample10))

Error: developer
          Df Sum Sq Mean Sq F value  Pr(>F)  
sex        1 4225.0  4225.0  9.6266 0.03613 *
Residuals  4 1755.6   438.9                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Error: Within
          Df Sum Sq Mean Sq F value Pr(>F)
area       5  77314   15463  1.2919 0.3066
sex:area   5  51892   10378  0.8671 0.5202
Residuals 20 239378   11969               
> summary(aov(productivity ~ sex * area, data=sample10))
            Df Sum Sq Mean Sq F value Pr(>F)
sex          1   4225    4225  0.4205 0.5228
area         5  77314   15463  1.5390 0.2153
sex:area     5  51892   10378  1.0330 0.4209
Residuals   24 241133   10047               
1つ目のaov()の出力より、このデータでは、性別による影響に有意差が見られるという結果になる。
2つ目のaov()の出力は、2要因とも対応なしとして計算すると、どの要因、交互作用にも有意差が見られないことを示すものである。

二元配置(1要因のみ対応あり)にする時のポイントを整理する。

  • 標本には個体差があることを前提とする
  • 1つの要因については、複数の水準に同じ個体からの標本があり、水準間でデータの対応が取れる
    例えば、同じ被験者から、各水準の条件下でデータが取られている
  • もう1つの要因については、1つの個体からの標本は1つの水準にしかなく、水準間でデータに対応がない
    例えば、水準を決める条件は同時に発生するので、水準毎に異なる被験者を選ぶ
もちろん、対応を決める要因は、個人や個体に限らず、標本に影響があることが既にわかっている条件であれば何でも良い。


二元配置(2要因対応あり)の標本のデータ構造は、1要因のみ対応ありのものよりも単純である。

二元配置(繰り返しあり、2要因対応あり)の例(1)
欠陥数
(steps/人月)
要因1:開発プロセス
WaterfallSpiralIncrementalTDD
要因2:
座席の形態
+ フェーズ
作業空間開発フェーズ
大部屋 仕様作成6673
全体設計4787
個別設計68105
テスト設計3264
実装6588
小部屋 仕様作成2635
全体設計5746
個別設計63104
テスト設計7564
実装12567
自宅 仕様作成5494
全体設計9296
個別設計7997
テスト設計5332
実装491011
要するに3次元なのである。ここでは表を立体的に書けないので、対応を決める「開発フェーズ」については列方向に展開している。
開発フェーズが違うと仕事の性質が全く違うので、フェーズの違いは当然ミスの数に影響する。
どんな開発プロセスにも、これくらいのフェーズは存在するので、各フェーズのデータはあり得る。また、どんな作業空間でも、一通りの開発をすれば全てのフェーズを経るので、各フェーズのデータが得られる。従って、異なる開発プロセスのデータ間でも異なる作業空間のデータ間でも、フェーズの違いによる対応が取れる。
> sample11 <- read.table("anova11.dat", header=TRUE)
> summary(aov(error ~ process * workspace - Error(phase/(process*workspace)), data=sample11))

Error: phase
          Df Sum Sq Mean Sq F value Pr(>F)
Residuals  4 94.733  23.683               

Error: phase:process
          Df Sum Sq Mean Sq F value  Pr(>F)  
process    3 30.850 10.2833  3.8482 0.03852 *
Residuals 12 32.067  2.6722                  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Error: phase:workspace
          Df  Sum Sq Mean Sq F value Pr(>F)
workspace  2  4.9333  2.4667  0.6251 0.5594
Residuals  8 31.5667  3.9458               

Error: phase:process:workspace
                  Df Sum Sq Mean Sq F value Pr(>F)
process:workspace  6  17.20  2.8667  0.5148 0.7912
Residuals         24 133.63  5.5681               
2要因対応ありのデータ構造は単純だが、その計算は、コンピューター任せでも複雑である。対応を決める要因の違いによる影響が、要因1との交互作用、要因2との交互作用、要因1と要因2と対応要因との交互作用と多岐に渡って分離して計算されるからである。
上記のaov()の構造式のError()の部分は、その構造がわかりやすいように
  • Error(phase + phase/process + phase/workspace)
  • Error(phase + phase:process + phase:workspace)
  • Error(phase + phase:process + phase:workspace + phase:process:workspace)
といった形で書かれることも多いようだが、ここではシンプルで入力ミスも避けられる
  • Error(phase / (process * workspace))
を採用している。
上の計算結果では開発プロセス間に有意差が出たが、同じデータを対応なしとして計算すると、次のようにどこにも有意差が出ない。
> summary(aov(error ~ process * workspace, data=sample11))
                  Df  Sum Sq Mean Sq F value Pr(>F)
process            3  30.850 10.2833  1.6904 0.1816
workspace          2   4.933  2.4667  0.4055 0.6689
 process:workspace  6  17.200  2.8667  0.4712 0.8262
Residuals         48 292.000  6.0833               

もう1つ例を考える。新規に参入した人がそこの設計業務をマスターするのに何ヶ月かかったかというデータが、次のように得られたとする。

二元配置(繰り返しあり、2要因対応あり)の例(2)
定着期間
(ヶ月)
要因1:設計手法
構造化設計データ指向オブジェクト指向コンポーネント指向
要因2:
開発内容
+ 性別
開発内容性別
プラットフォーム 男性 8 9 711
女性 3 6 7 7
ミドルウェア 男性 2 4 3 6
女性 9 5 9 8
フレームワーク 男性10 910 9
女性 910 511
アプリケーション 男性11 9 5 5
女性1210 1 5
男性と女性とでは元々差があるはずだという前提で、対応ありの分散分析を行う。
> sample12 <- read.table("anova12.dat", header=TRUE)
> summary(aov(takes_month ~ design_policy * target - Error(gender/(design_policy*target)), data=sample12))

Error: gender
          Df  Sum Sq Mean Sq F value Pr(>F)
Residuals  1 0.03125 0.03125               

Error: gender:design_policy
              Df  Sum Sq Mean Sq F value Pr(>F)  
design_policy  3 23.3438  7.7813  14.647 0.0269 *
Residuals      3  1.5938  0.5313                 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Error: gender:target
          Df Sum Sq Mean Sq F value Pr(>F)
target     3 45.844  15.281  0.8886 0.5375
Residuals  3 51.594  17.198               

Error: gender:design_policy:target
                     Df Sum Sq Mean Sq F value Pr(>F)
design_policy:target  9 95.531 10.6146  2.3142 0.1136
Residuals             9 41.281  4.5868               
> summary(aov(takes_month ~ design_policy, data=sample12))
              Df  Sum Sq Mean Sq F value Pr(>F)
design_policy  3  23.344  7.7812  0.9237 0.4422
Residuals     28 235.875  8.4241               
このデータでは、男女の違いによる対応ありとすると、設計手法の違いによる有意差が見られ、対応なしとすると、設計手法にも開発内容にもその違いによる差が見られないという結果になった。

続きを読む "統計学復習メモ19: 分散分析の種類"

2011年11月11日

 SVGで半透明図形の外側にドロップシャドーを付けるには?

暫くこのweblogを更新してないので、リハビリの為に何か書く。
これまで、代わりにTwitterとかをやってた訳ではなく、weblogとかを書きたく無くなった訳でもなく、ネタはあって細かいことは色々やってはいたのだが、何をやっても成果が出ないのである。考え事のネタだけが溜まっていく。物書き用のMacBookが不調で筆無精になってたり、部屋の地デジ化に時間を奪われたり、肺炎に罹ったりしたこともあったが、土日もクタクタで、そもそも頭を使うことに取り組む時間が激減してるので、トシなのだろう。

話を戻して、SVGでドロップシャドーを付けるには、SVG 1.1のドキュメント§15.1のサンプルコードのように、feGaussianBlurを使ってぼかしてずらしたものを先に描けば良いのだが、影を付ける対象が半透明だと、半透明の部分に自身の影が透けて暗くなってしまう。例えば、この方法で
dropshadow0.svg(注:この画像はJPEG、クリックするとSVGファイルが開きます)
この四角の上にある半透明の円に影を付けると、
dropshadow1.svg(注:リンク先はSVGのfilterに対応していないブラウザでは影が出ません)
このように、円の内側が暗くなってしまう。もし、円の外側にだけ影を付けたい場合、どうすれば良いだろうか。
手前に光源があれば、円の内側の半透明部分も影を作り出すんだから、円の内側に影が入り込むのは当たり前だろ、と言われればその通りである。この所、ブログの記事を書き始めてから問題設定に問題があることに気付いて、草稿をボツにすることが度々発生するのだが、今日はこのまま書き続ける。

上のSVGでは、SVG 1.1のドキュメントのサンプルコードに従って、

<filter id="dropShadow1" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
<feBlend in="SourceGraphic" in2="offsetBlur" mode="normal"/>
</filter>

このようなフィルターを定義しており、元の絵(SourceGraphic)と影との合成にfeBlendを使っているが、これをfeMergeに替えて
<filter id="dropShadow1" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
<feMerge>
<feMergeNode/> <!-- blurred and offset image -->
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>

にしても結果は同じである(feMergeはnormalモードでfeBlendするのと同じ)。

SVGでは、オブジェクトにα値がある限り、オブジェクトの重ね合わせにはα合成が発生するので、下の絵を消すかのようにα値ごと上書きすることはできない。<filter>〜<filter>の中では、feBlendやfeComposite等を使うことによりブレンドモードを変えることができ、特に<feComposite operator="arithmetic" k1="0" k2="0" k3="1" k4="0" />とすると重ねる絵で完全に上書きすることができるが、これらは矩形でしか行えないので、重ねる絵の何も無い部分(α=0のピクセル)も上書きして、下の絵を消してしまう。従って、

<filter id="shadow_normal" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
</filter>

これによってできる
dropshadow2.svg
こういう影の画像に元の絵を重ねるのではなくて、
dropshadow3.svg
こういう影の画像を作らないといけない。このdropshadow3.svgでは
<filter id="shadow_out" width="150%" height="150%">
<feGaussianBlur in="SourceAlpha" result="blur" stdDeviation="5"/>
<feOffset in="blur" result="offsetBlur" dx="10" dy="10"/>
<feComposite in="SourceAlpha" in2="offsetBlur" operator="arithmetic"
k1="0" k2="-100" k3="1" k4="0" />
</filter>

のようにして、元の画像のα値を-100倍して重ねることにより、元の画像のα > 0.01の部分のαを0にしている。これで目的が達成できているように見えるが、元の画像を重ねると
dropshadow4.svg
このように、少し隙間ができてしまう。これは、元の画像の輪郭の、輪郭を外れた少し外側に、アンチエイリアシングによって生じる微小なαを持つピクセルがあるからだと考えられる。上の例では右下に影を付けているので、影を左上にずらせば隙間を隠せるのではないかとも思うが、
dropshadow5.svg
この円の右上のように、隙間は右下にできるとは限らない。

次に、フィルターではなくクリップやマスクを使って影をカットすることを考える。まず問題になるのは、<clipPath>によって作られるクリッピング領域はパスの内側を残すクリップだということである。SVGではクリップやマスクを反転させる術は無いように思える。パスの外側を塗り潰すことが難しいのと同様に、パスの外側を残すクリップを作るのも難しいのである。
しかし、clip-ruleにevenoddを指定することによって穴開き領域を作ることができることを利用して、十分に大きな領域の内側に消したい領域の穴を開けたクリップを作ることは可能である。
そう思って、

<clipPath id="does_not_work" clip-rule="evenodd">
<rect x="0" y="0" width="200" height="200"/> <!-- 十分大きな領域 -->
<circle cx="80" cy="80" r="50"/> <!-- 消したい部分 -->
</clipPath>

のようにしてクリッピング領域を作ってみたが、これはrect部分からcircle部分が抜き取られるのではなく、rect部分とcircle部分の和になってしまった。evenoddは1つのパスで完結しないと働かないようである。
そこで、上記のrectとcircleをpathに置き換えて連結してみた。
<clipPath id="shadow_clip" clip-rule="evenodd">
<path d="M0,0 H200 V200 H0 V0
M130,80
C130,107.6 107.6,130 80,130
C52.4,130 30,107.6 30,80
C30,52.4 52.4,30 80,30
C107.6,30 130,52.4 130,80
Z"
/>
</clipPath>

これによって影を切り取ると
dropshadow6.svg
このようになり、元の絵を重ねると、
dropshadow7.svg
となり、所望の外側だけのドロップシャドーが得られた。

とりあえず目的は達成できたが、問題点として、
(1) SVGファイル内に似たようなパスデータが何度も現れる
(2) 影をつける対象は必ず1つのパスで書けなければならない
がある。(1)は似たようなパスでクリップと影と本体を別々に書かないといけないことによる。影と本体は、次のdropshadow8.svgのようにxlinkとuseを使ってパスを共通化できるが、クリップとの共通化は難しい。(2)とも共通する課題だが、複数のパスデータをclip-rule="evenodd"が利くように繋げる手段や、複数の要素をclip-rule="evenodd"で並べる手段が見つからないのが原因である。
dropshadow8.svg(xlinkを使って少しまとめたもの)

やはり問題設定が適切でないということだろうか。

続きを読む "SVGで半透明図形の外側にドロップシャドーを付けるには?"

2011年09月19日

 「DevLOVE関西2011CONNECT」傍聴録

9/17に、「DevLOVE関西2011CONNECT」とかいうアジャイル関連のイベントが府内であったので、行ってきた。

【聴いたセッション】

  1. オープニング
    遅れて入ったのであまり聴けず。
    Hanger Flightの話(初期の飛行士は、その体験を格納庫で話し合って共有することによって、飛行機の操縦という人類にとって新種の問題に取り組んだという話)とかしてた。
    1エンジニアの生涯工数は高々300人月くらいしかないから、1人で解決できることは知れてるとかなんとか。

  2. 「アジャイル開発事例と15分で作るRailsアプリ」(川端さん)
    XPの話、Javaで開発した事例紹介、Ruby on herokuの実演
    後述

  3. 「ゴール・リズム・愛って、プロジェクトの処方箋!?」
    プロジェクトファシリテーションとか
    後述

  4. 「障害管理からチケット駆動開発へ~BTSから始まる進化の歴史」
    Mantis最高、Mantis万能みたいな話
    後述

  5. PicoDialog
    「あなたはどんな時に能力を発揮していますか?」とか「どんな時に萎えますか?」とかのWebのアンケート5問を、1問ずつリアルタイムに集計しながら、各人の選択について参加者同士で話してみるというもの。
    4択だったのだが、いずれの質問においても、筆者に当てはまる選択肢が無く、すっきりしなかった。


【川端さんの話】
・XPの話
XPのテーマとして、Kent Beckの「白本」に始まり、それ以降のどの本でも言われていることは、コミュニケーション、フィードバック、シンプル、尊重、勇気の5つ。尊重と勇気がXPの特色だと思う。勇気とは、できる人がやったコードでも直す勇気のこと。
XPは、具体的なプラクティスが示されていることが良い。

XPが目指す成功とは、プロジェクトの成功というより、開発者満足+顧客満足だと思う。
開発者満足は従業員満足とはちょっと違う。従業員満足というと、給料が上がることとか休暇が取れることとかになるが、そういうことではなく、開発者が能力を発揮して成功できる、子供に憧れられる、尊敬される、自分が興味のある好きなことがができるようなこと。

「白本」のサブタイトルにもなってる"Embrace Change"(変化を抱擁する )という言葉が好き。レストランで魚料理を注文した客が、後になって肉料理に変更したいと言い出したら、もし料理ができ上がってても、もう作ってしまったので、と断るのでなく、もしもう作っている途中であってもそれを見せることなく、注文の変更を受け入れること、また注文の変更がしやすいように思わせること、が目指す理想だということ。

・Javaでの開発事例
COBOLだった金融システムをJavaに置き換えるためのフレームワークの開発を担当した時、発注者がアジャイルに理解があったので、やり方を全て自分達で決めてそれも都度変更していくアジャイル流で行った。

オブジェクト指向の知識も無かったが、リッツ・カールトンの「クレド」の話を知って、それを参考にしたり、進め方を自分達で決めてマインドマップにまとめるということをやって目的意識と自主性を高め、2週間のイテレーションを毎回確実にやり切り、20回のイテレーションだったが、最後まで週40時間のペースで終わらせることができた。

その過程で何を工夫したか、どんなトラブルがあってどう解決したかの話も色々あったが、筆者がポイントを掴めなかった為、カットする。

・Ruby on herokuのデモ
Rails, heroku等のインストールからscaffoldを使ったWebアプリの作成、herokuとgitを使ったインターネットへの公開まで、15分くらいで実演された。色々なConfigファイルの類を書き換えてたのが数ヶ所、アプリコードを書き換えてたのが1〜2行くらいで、ほとんどの時間はRubyのコンソールを操作してインストールしたりmakeしたりしていた感じだった。

打ち合わせの最中にも動くものが作れてしまう、動く仕様書なので意思伝達の齟齬も無い、とかいうアピールもされた。

(感想)
このイベントのWebページのこのセッションの表題の所に「バグがないプログラムのつくり方 JavaとEclipseで学ぶTDDテスト駆動開発」と書いてあって、実はこれに興味を持ったから参加したのだが、これは今回のセッションのテーマではなく、川端さんの著書のタイトルだったようだ。何と紛らわしい。かなり当てが外れたが、終始興味深い話で、Ruby on Railsの実演はなかなか鮮烈だったので、満足だった。

まあしかし、ある物を単に組み合わせるだけで作れる範囲なら短時間で作れるというのはよくある話であって、その範囲を少しはみ出ると途端に大変になるというのもよくある話である。特にお仕着せのオートマチックなフレームワークであるほどありがちである。まあ面白かったなということだろう。

筆者にはどうもRubyは遊びに見えてしまう。"Rails", "scaffold", "rake"といった名前の付け方からしてもそれは思う。新しいこと、面白いことを好むRubyな人達にとっては、実用的、実践的な名前の付け方は「つまらない」のだろう。


【ゴール・リズム・愛って、プロジェクトの処方箋!?】
アジャイルを10年やって、やはりソフトウェアは人が作るものだということを意識している。
メンバーのパワーを何%出せてるか、を考える。やり方がまずくて30%しか出せてないとか、100%を出しているがやらされ感があってクタクタとかいうことがある。
しかし、例えば運動会では100%以上の力を出せてると思う。それの実現を目的とするのがプロジェクトファシリテーションである。

プロジェクトマネジメントは計画達成型のマネジメント、
プロジェクトファシリテーションは参加者の協調の場作り。

運動会で100%以上の力が出せるのは、目的が明確で、かつ、メンバー間で一致しているから。
ゴールが明確でなかったりメンバー間で共有できていなかったりすると、チームとして100%の力を発揮するのは不可能である。また、ゴールが漠然としていると、その漠然としたレベルではチームで共有できていても、具体的なゴールの認識にはずれがあるもので、それもパフォーマンスの低下に繋がる。課題とはゴールと現実とのギャップでしかなく、ゴールがずれていれば課題は解決しない。

アジャイルのイテレーションが有効な理由は、遠いゴールを目指すのでないこと。長期的なゴールがメンバー間で一致していても、短期的なゴールにはずれが生じるものである。
WBSがなぜ階層化して分割するかというと、目の前のゴールがはっきりしていると走りやすいからである。

ソフト開発はメンバー間でリズムが一致していると良いと思う。例えば、毎日決まった時間にミーティングするとか。しかし、経験上、そのミーティングを夕方以降にすると「今日も大変だったね」だけになりがちなので、朝にするのが良いと思う。朝やると目標の確認になる。
朝会では、何か笑顔になることをやってみるといい。ハイタッチとかのつまらないことでも、自然に笑顔になる効果がある。すぐ飽きるが、飽きたら次のネタを考えればいい。それでファシリテーションになる。

愛とは、結局、欲望だと思う。人それぞれ欲望は異なる。異なっても、それにより違うtryが行われるので良いのである。

ピーターパンは、"Let's fly"とは言わず、"You can fly"と言う。相手によってその結果はまちまちだが、それで最終的に"We can fly"になる。

(感想)
宗教的な香りが漂うアジャイルの中で、しかも人間相手で掴み所がなく、極めて答えが少ないテーマに関わらず、相当にわかりやすく実践的な話だと感じた。ファシリテーションやコーチングに答えは無い。あるのは目的だけだ。テーマが広すぎて迷子になるよりは、このように具体的なヒントがある話にする方が幾分かマシであろう。

ただ、目の前のゴールの認識にもメンバー間でずれがあるもの(それがチームのパフォーマンス低下の一因)という話は、比較的難しい問題が置き去りにされていると感じた。メンバー間の認識にはずれがあるものだということを理解して話を聞けば、自ずと道は開ける、だろうか。マネージャーの本分は道を見つけること、リーダーの本分は道を示すこと、そのどちらもメンバーの知識や理解力に偏りがあるほど、衝突するものである。運動会のように競争本能によって労せず目的を一致できるものではないし、メンバー全員がゴールを共有できるとは限らないものである。
個人的にはむしろ、メンバー全員がゴールを共有しない限りは団結できないと考える方が危ういのではないか、と思ったりもする。


【障害管理からチケット駆動開発へ】
本来のTiDDとは何なのか?
BugzillaでTiDD以前のことをやってた時は、軽量さと規律の両立の妙、をあまり理解してもらえなかった。

Excelによる障害管理には課題があった。バグ情報が散在して混乱しやすい、作業履歴がExcelやメールに散らばる、集計や報告書作りに手間がかかる、バグ修正と検証のワークフローが複雑で、たらい回しにされることもある、管理台帳でリリースとの関連を管理する必要があるなど、リリース管理が大変、リリース履歴からバグを探しにくい、等々。

MantisはPHPで作られているOSSのBTSで、バグ管理に特化している、Web I/Fを持つシステム。障害はチケットで一元管理し、チケットのステータス遷移でワークフロー制御でき、検索や集計が楽で、終了チケットをリリース履歴として残すことができる、等々。

Redmineと同じく、No ticket, no commitのプラクティスが実践できる。

平均完了日数がわかる。これでチケットの粒度がわかる。イテレーションのサイクルに関係してると思う。
あるプロジェクトで、MTBFを求めてみたら、 1ヶ月持たずにバグが出ることがわかった。

BTSを仕様変更や課題管理にも使いたい。バグ修正のワークフローはSW開発の基本フローである。BTSからITS(Issue Tracking)へ。
BTSのチケットはXPのタスクカード、アジャイルのストーリーカード。BTSはアジャイルと対応付けができ、アジャイルそのもの。
Mantisは柔軟なワークフロー管理ができ、並行開発やブランチもサポートできる。

TiDDは組織全体(全ての業務)へ展開可能。TiDDはBTSのベストプラクティスを引き継いでいる。TiDDはメンバーの自発性が出てくる。TiDDはXPと同じくプログラマ復権運動。

(感想)
TiDDがBTSから派生したものだというのはそうだが、それを言うならMantisとRedmineの比較がされるべきだった。Mantisだけを取って何々ができる、というのは本質的ではないし、厳しく言うと、Excelを比較対象としているが、BTSに必要な機能として、MantisでできてExcelでできないことは何一つ説明されなかった。基本的に、Mantisと同じ入力フォームやデータ管理はExcelで実現可能である。Excelで障害管理する上での課題として挙げられたことは全て、障害管理そのものが理解されずにExcelが使われた結果であり、障害管理のノウハウはMantisを使う上でも必要なのであり、Mantisを有効に使うスキルがあればExcelで管理しても挙げられたような課題は起こらないのである。

Webのシステムだから使いやすい、というのはナンセンスである。Webのシステム(ネットワークシステム)であることとWebブラウザをI/Fとして使うシステムであることは違うし、HTMLに制限されたUIを持つシステムが一般に不便であることは常識であり、むしろExcelからのインポート、Excelへのエクスポート機能が求められることが多いものである。WebブラウザをI/Fとして使う利点はplatform independentであることだけであり、Webのシステムの利点はinteroperability、つまり他のWebベースのシステムとの連携が容易(である可能性が高い)ということである。どちらにしても、MantisやRedmineの長所となるものではない。

筆者の経験上、実務的にBTSに最も求められる機能は、検索/集計と、バックトラッキング(トレーサビリティ)である。それはMantis/Redmineは構成管理ツールと連携して実現しているが、その他の障害管理システムでも何らかの形で実現しているものである。ソースコードのリリースとバグ票番号の対応が取れない構成管理システムはあり得ない。問題はできるかどうかではなく、やり易いかどうかである。MantisやRedmineでも、チケットとコミットとの関連付けを、人の手でチケット番号やタグの入力によって行うのであれば、Excelでも同じことをすればコミットからのバックトラッキングができるのである。

TiDDはあらゆる業務に使えるというのは根本的におかしい。No ticket, no commit(or no work)というのはトレーサビリティの話であり、本来は要件番号や要求仕様書との関連や業務指示書のIDを明確にすることによってMantisやRedmineを使わなくてもトレース可能な情報を残すべき話である。「チケット」だからどうという話ではないのであり、「チケット」でも単位作業の粒度が問題になる話であり、「チケット」ベースだから確実という話でもないのである。何もかも「チケット」ベースにすることが可能であることと、何もかも「チケット」ベースにする方がやり易いことは全く異なる。手段が目的になってしまっている話であり、道具に使われてしまっている話である。そのような発想では、チケットの海に沈むことになり、Excelでやってた頃と同じ状況に逆戻りしてしまうであろう。

MTBFを求めてみたという話があったが、ソフトウェアのバグが確率的にしか起こらないのであれば意味があるかも知れないが、再現手順が明確になったバグはMTBFと関係なく起こせるのであり、1つのバグのTBFは1回しかないので、本当の意味のMTBFではない。
例えばもし、修正すべき不具合は平均的に1ヶ月に1件起こるというデータが出るのであれば、毎月1件分の修正コストを見込んでおけばいいなどの使い方ができるので意味があると思うが、平均を求めたら1ヶ月に1件だった、というのと、平均的に1ヶ月に1件起こるのとは違うのである。
ソフトウェアは、初めの頃に不具が合多く発見され、次第に安定して発見される不具合が少なくなるものである。従って、平均を取る期間を長くすればするほど平均値が減るものであり、そういう平均値は意味が無いのである。推定量の一致性がないというやつだ。また、ばらつきの有無も問題である。平均が1件/月でも、3件の月が1回で0件の月が2回であれば、平均値に意味が無いのである。
そこまで考慮して、実測したら意味のあるMTBFを取れたという話なら有益だったが、今回の話はそういうものではなかった。やはりソフトウェアの不具合にMTBFを持ち出すのはナンセンスであろう。これも本質を捉えずに拡大解釈したことにより無意味に混乱した話だと思った。

続きを読む "「DevLOVE関西2011CONNECT」傍聴録"

2011年07月30日

 クロソイド曲線

x'=\cos\left(\frac{\pi t^2}{2}\right)
y'=\sin\left(\frac{\pi t^2}{2}\right)
というシンプルな数式で表されるそうなので、Maximaでその数式でグラフを描いてみようとしたが、残念ながら媒介変数を含む微分方程式をそのまま描画することはできないようだった。
微分方程式をグラフ化する関数としてplotdf()があるが、parametric plotができないようだ。
integrate()で定積分したものをplot2d()でparametric plotすればいいかと思ったが、残念ながら上記の関数のintegrate()は計算されなかった(erf混じりの非数値解になる)。

上記のx',y'を定積分したものはフレネル積分と呼ばれるそうで、Maximaにfresnel_c(), fresnel_s()があったので、今回はそれを使うことで良しとした。

●Maximaへの入力

plot2d([parametric, fresnel_c(t), fresnel_s(t), [t,-6,6]],[x,-1,1],[y,-1,1],[style,lines],[nticks,1000]);

●出力(ブラウザがSVGを表示できない場合→ PNG
Clothoid curve

車のハンドルを一定の速度で回していくとできる曲線だそうだ。

続きを読む "クロソイド曲線"

2011年07月18日

 JavaアプレットでSVGを再生する(2)

以前にBatikを使ってJavaでSVGを描画したり書き換えたりしてみたが、特にWebブラウザ上で動かすJavaアプレットを作成する目的ではあまり満足のいく結果が得られなかったので、もう1つ気になっていた、軽量が売りのSVG Salamanderも使ってみることにした。

SVG書き換え再生テストの起動ページ

ソースコード
svgSalamanderTest1.java アプレット本体
SVGIconOnJPanel.java アイコン描画用にダブルバッファリングを有効にしたJPanel
BouncingIcon.java
SVGファイル
txtanitest1s.svg 文字列書き換えテスト用SVG
p_icon.svg アイコンのSVG

前回と同様に、SVGの文字列を動的に書き換えて再生する。このSVGの表示には、JPanelを継承していてJAppletに組み込みやすいSVGDisplayPanelクラスを使っている。
それとは別に、SVGIconクラスはjava.awt.Graphicsに描画できるので、Swingのコンポーネント上も含めて、アイコンを動かしながら表示してみた。
ちなみに、Popボタンを短時間に連打するとアイコンが初期サイズまで戻らなくなるのは、仕様である。


以下、今回何か作ってみてわかった、SVG Salamanderを使う上での注意事項をまとめる。

まず、再生可能なSVGはかなり限られると思っておいた方が良い。SafariやBatikのSquiggleでは思い通りに再生できるのにSVG Salamanderでは思い通りに再生されないということは頻繁に起こった。それがSVG的に正しくない(SafariやBatikは気を利かせて補間している)からなのかSVG Salamanderの対応が不十分なのかはあまり追究していないが、何せ、SVG Salamanderを使うなら、svgSalamander.jarを実行すると起動するSVGプレーヤーで再生確認しながら、SVGファイルを編集することになる。

SVG Salamanderの描画がSafariやBatikと違ってしまう要因の1つは、ECMAScriptが使えないことである。<script>タグが含まれるSVGファイルはSVG Salamanderでは正しく再生されないのである。動的なSVGコンテンツにはよくECMAScriptが使われるので、どこかから拾ったSVGはこれが原因で再生されないことが少なくない。

他に、SVG SalamanderのSVGの扱いに関して筆者が気づいたこととして、パスデータ(<path>タグのd属性などで与えられるもの)に、"C"/"c"(curveto)や"Q"/"q"(quadratic Bézier curveto)が先行せずに"S"/"s"(smooth curveto)や"T"/"t"(smooth quadratic Bézier curveto)があると、まず期待した動作にならない。SafariやBatikでは1つ目のcontrol pointを始点(またはその閉パスの最後のcontrol point?)とすることで突拍子も無い表示になることを避けているようだが、SVG Salamanderは2つ目のcontrol point或いは終点の、始点からの反対方向の鏡像点を1つ目のcontrol pointとしている節がある。


次に、Javaライブラリに関して気付いたことをまとめる。

SVGのテキスト要素の文字列の先頭や終端に余分な空白があると、描画時とText#getBoundingBox()実行時とで空白の扱いが異なってしまうようで、内部の計算位置と表示位置にずれが発生してしまう。これはSVGファイルの<text>〜</text>で括られたテキストの先頭や末尾の改行や空白についても同じことが起こるので、Javaコード内だけでなくSVGファイル内でも空白文字を消しておく必要がある。

SVGにフォントサイズのアニメーションがあると、SVGUniverse#updateTime()では表示に反映されないようだ。内部的に値は更新されるようで、Text#rebuild()すれば文字サイズに反映されるが、透明度のアニメーションと組み合わせると、ちらつく上、Text#rebuild()時に変化中の透明度が無視されて不自然な表示になった。

SVGElementクラスにhasAttribute(), setAttribute()があるのにgetAttribute()が無いのが謎である。現在の値からいくらか変化させるということができないことを意味する。
また、現実的な問題として、printfデバッグのために、その時点での属性の値を取得したいということはよくあったが、その手段が無かったのが結構辛かった。

それから、色々なクラスのset系のメソッドの引数にある、attribTypeの意味がわからない。JavadocのAPI referenceには与え得る値も何も書かれていない。今回は"Using SVG Salamander"のページのサンプルコードに書かれているAnimationElement.AT_XMLを一律に指定しているが、これで正しいのかどうかはわからない。

BatikのJSVGCanvasはSVGの表示位置が自動的に中央寄せになるが、SVG SalamanderのSVGDisplayPanelでは左寄せになってしまう。しかも、中央寄せにするメソッドが無い。SVGファイルの<svg>タグのviewBox属性を書き換えれば位置調整ができるが、メモリにロードしたviewBox属性を更新する手段がない。SVGDiagram#getRoot()で取れるSVGRootのprivate属性にはあることがわかったが、getAttribute()メソッドが無いので、値を取得することすらできない。
今回は、SVGDiagramのgetViewRect()とgetDeviceViewport()で得られた座標を元にSVGのviewBox属性を直接書き換えるという強引な方法で、何とか中央寄せができた。(svgSalamanderTest1.javaのcenterViewBox()参照)


最後に、Webサーバーに配置する時の注意点として、svgSalamander.jarはSVGプレーヤーが内蔵されていたり署名されていたりするので、そのSVGプレーヤーも署名する必要も無ければ、ライブラリとしての実行用にはsvgSalamander-tiny.jarを使うと良い。

続きを読む "JavaアプレットでSVGを再生する(2)"