2012年01月10日
統計学復習メモ19: 分散分析の種類
分散分析というと、その名前自体によく「一元配置」「二元配置」とか「対応あり」「対応なし」とか「繰り返しのある」とか「繰り返しのない」とかいう言葉がついて回る。統計学の書籍でも、「一元配置分散分析」と「二元配置分散分析」は項目を分けて説明されることが多い。さらにそれぞれが「対応あり/なし」「繰り返しあり/なし」等で分けられると、目次に「分散分析」がたくさん並ぶことになる。Excelの「分析ツール」のメニューにも分散分析の項目があるが、
- 分散分析: 一元配置
- 分散分析: 繰り返しのある二元配置
- 分散分析: 繰り返しのない二元配置
筆者は未だに、どういう時にどの種類の分散分析を使うべきなのかよくわかっていない。 頭の中を整理するために、いつものように体当たり的に、
- 一元配置(対応なし)
- 一元配置(対応あり)
- 二元配置(繰り返しなし)
- 二元配置(繰り返しあり、対応なし)
- 二元配置(繰り返しあり、1要因対応あり)
- 二元配置(繰り返しあり、2要因対応あり)
以下のデータは、全て架空のものである。
筆者はExcelを持っていないので、分散分析の計算にはRを使う。Rにも分散分析の関数はoneway.test(), aov(), anova(), lme()など色々あるが、今回は上記の全てをカバーできるaov()を使う。
最もシンプルな、一元配置(対応なし)の例として、次のようなデータを考える。
| 要因:モジュールの階層 | |||
|---|---|---|---|
| アプリ | ミドル | ドライバ | |
| 生産性 (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で有意であることを表す。分散分析の帰無仮説は「全ての標本は同じ分布に従う母集団から得られたもの」→「各群の分布が同じであること」→「群間にばらつきがないこと」なので、分散比が有意であれば、「全体のばらつきに対して群間にばらつきがないとは言えない」、細かいことを言わなければ、群間にばらつきが認められ、各群の分布は同じでないことになる。
この例では、生産性はモジュールの階層によって違いがありそうということになる。
次の例は、同じ観測値を別の要因(観点)で分けたものである。
| 要因:所属 | |||
|---|---|---|---|
| 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つ例を考えた。
| 要因:仕様書のファイル形式 | ||||
|---|---|---|---|---|
| .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つ以上あるように選ぶこと - 各水準のデータ数は同じでなくても良い
- 水準はなるべく定量的な数値でないこと、数値であれば、なるべく測定値の従属変数でないことが明確であること
(要因と測定値に明確な関係があっても、群間のばらつきが十分に大きくないと有意差が検出できないため。また、水準(値の範囲)の取り方によって結果が変わってしまうため。例えば測定値と線形の関係にあるなら、無相関の検定をする方が良い)
一元配置(対応あり)の例として、次のようなデータを考える。
各開発者がその4種類の開発プロセス(作業手順)を経験した時の、それぞれの開発プロセスにおける成績が、次のように得られたとする。
| 生産性 (steps/人月) | 要因:開発プロセス | ||||
|---|---|---|---|---|---|
| Waterfall | Spiral | Incremental | TDD | ||
|
対 応 要 因 |
開発者A | 490 | 750 | 450 | 610 |
| 開発者B | 410 | 480 | 530 | 780 | |
| 開発者C | 460 | 500 | 240 | 680 | |
| 開発者D | 590 | 650 | 550 | 880 | |
| 開発者E | 500 | 770 | 350 | 520 | |
| 開発者F | 370 | 610 | 730 | 600 | |
| 開発者G | 460 | 310 | 500 | 400 | |
| 開発者H | 690 | 720 | 640 | 1030 | |
開発者の能力や向き不向きには個人差があることを前提とし、それを計算に入れて、開発プロセスは生産性に影響するかどうかを、分散分析で調べる。
> 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
実際には各開発プロセスを短期間に経験することは難しく、開発者の経年変化や学習効果もあるので、こういうデータは取りにくいが、例えば開発者がソフトハウスのことであり、ソフトハウス内ではばらつきが無いと仮定すれば、各プロセスを同時に行うことも可能であるし、何より、筆者がこれ以上単純明快な例を思い付かないので、これで良しとする。
次の例は、会社による差(観測対象の個体差)はあるものとして、業務分野の違いが生産性に影響すると言えるかどうかを調べる。
| 生産性 (steps/人月) | 要因:開発分野 | ||||
|---|---|---|---|---|---|
| 組み込み | 汎用 | 業務システム | ゲーム | ||
|
対 応 要 因 |
M社 | 330 | 470 | 450 | 320 |
| N社 | 290 | 440 | 370 | 630 | |
| O社 | 450 | 550 | 580 | 750 | |
| P社 | 320 | 360 | 470 | 460 | |
| Q社 | 370 | 320 | 430 | 480 | |
> 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つでなく複数あっても計算可能(但し全てのセルのデータ数が同じであることが望ましい)
二元配置(繰り返しなし)の例として、次のようなデータを考える。
| 生産性 (steps/人月) | 要因1:開発プロセス | ||||
|---|---|---|---|---|---|
| Waterfall | Spiral | Incremental | TDD | ||
|
要因2: 座席の形態 | 大部屋 | 490 | 700 | 450 | 560 |
| 自由席 | 410 | 430 | 530 | 730 | |
| パーティション | 460 | 450 | 240 | 630 | |
| 小部屋 | 590 | 720 | 550 | 830 | |
| 自宅 | 500 | 600 | 350 | 470 | |
> 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つずつ、データを採取したとするものである。
| 欠陥数 /kstep |
要因1:開発期間 | |||
|---|---|---|---|---|
| 1週間 | 2週間 | 1ヶ月 | ||
|
要因2: 曜日 | 月曜 | 10 | 6 | 4 |
| 火曜 | 6 | 9 | 6 | |
| 水曜 | 8 | 6 | 4 | |
| 木曜 | 4 | 9 | 4 | |
| 金曜 | 8 | 6 | 4 | |
| 土曜 | 10 | 9 | 9 | |
| 日曜 | N/A | 6 | 9 | |
> 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つ
- どちらの要因も何らか影響していると思われ、それぞれの影響を分離して調べようとしていること
- 一部のデータが抜けていても計算可能
- 一元配置(対応あり、繰り返しなし)の代用として行うことも可能
Excelの分析ツールに「対応のある一元配置」が無いのは、二元配置で代用できるからだろう。
二元配置(繰り返しあり、対応なし)は、表の形としては、繰り返しのない二元配置の各セルに複数のデータが含まれるだけの違いなので、同じような要因のペアが使える。
| 生産性 (steps/人月) | 要因1:開発プロセス | ||||
|---|---|---|---|---|---|
| Waterfall | Spiral | Incremental | TDD | ||
|
要因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つ例を作る。
| 結合テストエラー数 /kstep |
要因1:仕様書の配布形態 | ||||
|---|---|---|---|---|---|
| Word | Excel | HTML | |||
|
要因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つの要因の交互作用が混入する
- 交互作用によるばらつきを分離して計算するかどうかは場合による
- 一元配置(対応あり、繰り返しあり)の代用として行うことも可能
「二元配置(1要因対応あり)」は、「対応のある要因と対応のない要因の二元配置」と書かれることが多いようだが、そう書かれると余計にややこしく見えるのは、筆者だけだろうか。
「対応あり」とは、一元配置の場合と同様、繰り返しのある(セル内に複数ある)データのそれぞれが、他の水準(セル)のどのデータに対応するかがわかる、という意味であり、対応を取るからには、個体差の影響を取り除いて計算する必要があると考えていることになる。
1要因についてのみ対応が取れる状況として、次のような例を考える。
| 生産性 (steps/人月) | 要因1:開発プロセス | |||||
|---|---|---|---|---|---|---|
| Waterfall | Spiral | Incremental | TDD | |||
|
要因2: 座席の形態 + 人 | 作業場 | 開発者 | ||||
| 大部屋 | A | 630 | 660 | 570 | 340 | |
| B | 530 | 430 | 430 | 400 | ||
| C | 390 | 640 | 520 | 530 | ||
| パーティション | D | 400 | 760 | 500 | 680 | |
| E | 550 | 670 | 290 | 890 | ||
| F | 650 | 620 | 280 | 820 | ||
| 小部屋 | G | 470 | 680 | 760 | 410 | |
| H | 530 | 580 | 670 | 930 | ||
| I | 850 | 980 | 790 | 980 | ||
| 自宅 | J | 610 | 850 | 530 | 310 | |
| K | 400 | 640 | 230 | 400 | ||
| L | 530 | 430 | 470 | 330 | ||
前記のように同じ人が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つの分野の業務を行った時の生産性を測ったという例である。
| 生産性 (steps/人月) |
要因1:性別 + 人 | |||||||
|---|---|---|---|---|---|---|---|---|
| 性別 | 男性 | 女性 | ||||||
| 開発者 | A | B | C | D | E | F | ||
|
要因2: 業務分野 | ||||||||
| 組み込み(アプリ) | 470 | 720 | 550 | 610 | 580 | 550 | ||
| 業務システム | 490 | 530 | 530 | 570 | 310 | 380 | ||
| 生産システム | 530 | 520 | 570 | 600 | 520 | 490 | ||
| PCアプリ | 530 | 310 | 460 | 420 | 690 | 650 | ||
| Javaアプリ | 600 | 530 | 500 | 450 | 790 | 590 | ||
| ゲーム機ソフト | 450 | 530 | 400 | 530 | 350 | 530 | ||
開発者の能力には当然個人差がある。 全開発者が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要因のみ対応ありのものよりも単純である。
| 欠陥数 (steps/人月) | 要因1:開発プロセス | |||||
|---|---|---|---|---|---|---|
| Waterfall | Spiral | Incremental | TDD | |||
|
要因2: 座席の形態 + フェーズ |
作業空間 | 開発フェーズ | ||||
| 大部屋 | 仕様作成 | 6 | 6 | 7 | 3 | |
| 全体設計 | 4 | 7 | 8 | 7 | ||
| 個別設計 | 6 | 8 | 10 | 5 | ||
| テスト設計 | 3 | 2 | 6 | 4 | ||
| 実装 | 6 | 5 | 8 | 8 | ||
| 小部屋 | 仕様作成 | 2 | 6 | 3 | 5 | |
| 全体設計 | 5 | 7 | 4 | 6 | ||
| 個別設計 | 6 | 3 | 10 | 4 | ||
| テスト設計 | 7 | 5 | 6 | 4 | ||
| 実装 | 12 | 5 | 6 | 7 | ||
| 自宅 | 仕様作成 | 5 | 4 | 9 | 4 | |
| 全体設計 | 9 | 2 | 9 | 6 | ||
| 個別設計 | 7 | 9 | 9 | 7 | ||
| テスト設計 | 5 | 3 | 3 | 2 | ||
| 実装 | 4 | 9 | 10 | 11 | ||
開発フェーズが違うと仕事の性質が全く違うので、フェーズの違いは当然ミスの数に影響する。
どんな開発プロセスにも、これくらいのフェーズは存在するので、各フェーズのデータはあり得る。また、どんな作業空間でも、一通りの開発をすれば全てのフェーズを経るので、各フェーズのデータが得られる。従って、異なる開発プロセスのデータ間でも異なる作業空間のデータ間でも、フェーズの違いによる対応が取れる。
> 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つ例を考える。新規に参入した人がそこの設計業務をマスターするのに何ヶ月かかったかというデータが、次のように得られたとする。
| 定着期間 (ヶ月) | 要因1:設計手法 | |||||
|---|---|---|---|---|---|---|
| 構造化設計 | データ指向 | オブジェクト指向 | コンポーネント指向 | |||
|
要因2: 開発内容 + 性別 |
開発内容 | 性別 | ||||
| プラットフォーム | 男性 | 8 | 9 | 7 | 11 | |
| 女性 | 3 | 6 | 7 | 7 | ||
| ミドルウェア | 男性 | 2 | 4 | 3 | 6 | |
| 女性 | 9 | 5 | 9 | 8 | ||
| フレームワーク | 男性 | 10 | 9 | 10 | 9 | |
| 女性 | 9 | 10 | 5 | 11 | ||
| アプリケーション | 男性 | 11 | 9 | 5 | 5 | |
| 女性 | 12 | 10 | 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を使って少しまとめたもの)
やはり問題設定が適切でないということだろうか。
2011年09月19日
「DevLOVE関西2011CONNECT」傍聴録
9/17に、「DevLOVE関西2011CONNECT」とかいうアジャイル関連のイベントが府内であったので、行ってきた。
【聴いたセッション】
- オープニング
遅れて入ったのであまり聴けず。
Hanger Flightの話(初期の飛行士は、その体験を格納庫で話し合って共有することによって、飛行機の操縦という人類にとって新種の問題に取り組んだという話)とかしてた。
1エンジニアの生涯工数は高々300人月くらいしかないから、1人で解決できることは知れてるとかなんとか。 - 「アジャイル開発事例と15分で作るRailsアプリ」(川端さん)
XPの話、Javaで開発した事例紹介、Ruby on herokuの実演
後述 - 「ゴール・リズム・愛って、プロジェクトの処方箋!?」
プロジェクトファシリテーションとか
後述 - 「障害管理からチケット駆動開発へ~BTSから始まる進化の歴史」
Mantis最高、Mantis万能みたいな話
後述 - 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を持ち出すのはナンセンスであろう。これも本質を捉えずに拡大解釈したことにより無意味に混乱した話だと思った。
2011年07月30日
クロソイド曲線


というシンプルな数式で表されるそうなので、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)
車のハンドルを一定の速度で回していくとできる曲線だそうだ。
2011年07月18日
JavaアプレットでSVGを再生する(2)
以前にBatikを使ってJavaでSVGを描画したり書き換えたりしてみたが、特にWebブラウザ上で動かすJavaアプレットを作成する目的ではあまり満足のいく結果が得られなかったので、もう1つ気になっていた、軽量が売りのSVG Salamanderも使ってみることにした。
ソースコード
・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を使うと良い。
![Validate my RSS feed [Valid RSS]](images/valid-rss.png)