使用環境
Claris FileMaker Pro Version 21.1.1.41 (macOS)
使用データ
MLB 公式 BaseballSavant STATCAST Player Batting, Qualifiers, 2024
2024年シーズンの規定到達の打者129名の基本データ
ダウンロードした stats.csv を名前変更(テーブル名、ここでは player_batting と仮定)した上で、新規テーブルにインポート。
1行目は、フィールド名としてインポートする。
プラン
インポートして作った player_batting テーブルにある batting_avg フィールド(各選手の打率)は分布するということを調べる。
① 以下を計算して、データを数値要約する
- 総和
- 平均値
- 最大値
- 最小値
- 中央値
- 範囲
- 分散
- 標準偏差
- 母集団分散
- 母集団標準偏差
② 棒グラフで代用して、ヒストグラムを描く
③ 度数分布表を描く
ルール
- FileMaker Pro 特有の計算フィールドおよび集計フィールドは使わない
- スクリプトステップ Loop は使わない
ソリューションファイルの準備
FileMaker Pro で新規作成を行うと初期テーブルが作成されますが、これは名前変更 session
としてインポートには使用しません。
ダウンロードしたデータは新規テーブル、テーブル名 player_batting としてインポートします。
インポートしたデータの最初のフィールドはlast_name, first_name
となっており、半角スペースが含まれています。このフィールドを扱うときは、変更した方がいいでしょう。
この段階で、テーブルオカレンスは、session
、player_batting
の2つですが、意味的な分類で、色分けしておくことをお勧めします。
session
を1つ、player_batting
を2つ複製し、名前変更します。以下のように命名します。
ベーステーブル | テーブルオカレンス名 |
---|---|
session | _SESSION |
player_batting | _PLAYERBATTING |
player_batting | __playerbatting_PLAYERBATTING |
_PLAYERBATTING
と __playerbatting_PLAYERBATTING
はデカルト積で自己リレーションシップを作成します。ここでは、フィールド player_id
、year
同士を「x」記号で繋げています。
レイアウト作成(仮)
以下のようにレイアウトを作成します。
レイアウト名 | テーブルオカレンス名 | 配置するフィールド |
---|---|---|
_SESSION | _SESSION | 現時点でなし |
_PLAYERBATTING | _PLAYERBATTING | なし |
__playerbatting_PLAYERBATTING | __playerbatting_PLAYERBATTING | batting_avg |
OnWindowFirstOpen 用のスクリプト作成
とりあえず、空のスクリプトを以下の名称で作成します。
スクリプト名 |
---|
OnFirstWindowOpen |
PlayerBattingStats |
OnFirstWindowOpen
スクリプトをスクリプトトリガ OnFirstWindowOpen
に設定します。
ファイル > ファイルオプション... > スクリプトトリガ > ☑️ OnFirstWindowOpen「OnFirstWindowOpen」
OnFirstWindowOpen
スクリプトは、起動時に、レイアウトを切り替え、計算を実行させるために使用します。以下のスクリプトステップを設定します。
レイアウト切り替え [ 「_PLAYERBATTING」 (_PLAYERBATTING) ; アニメーション: なし ]
スクリプト実行 [ 指定: 一覧から ; 「PlayerBattingStats」 ; 引数: ]
グローバル変数のレイアウトへの配置
今回は、フィールドにはデータを保存しないで、グローバル変数に計算結果を表示する方法を採ります。
_PLAYERBATTING
レイアウトに以下のグローバル変数を配置します。
グローバル変数は、レイアウトモードで、挿入 > マージ変数
で配置できます。
見出し | グローバル変数名 |
---|---|
レコード数 | $$LENGTH |
総和 | $$SUM |
平均値 | $$MEAN |
最大値 | $$MAX |
最小値 | $$MIN |
中央値 | $$MEDIAN |
範囲 | $$RANGE |
分散 | $$VAR |
標準偏差 | $$SD |
母集団分散 | $$VARP |
母集団標準偏差 | $$SDP |
母集団は、129名の選手しかいないので、分散、標準偏差を出す必要はありませんが、流用しやすいように加えています。
FileMaker Pro の関数による計算
計算はすべて、PlayerBattingStats
スクリプト内の計算式で行います。まず、最初と最後を示します。
ウインドウの固定
全レコードを表示
レコード/検索条件/ページへ移動 [ 最初の ]
#
# ここから計算
# ここまで
#
レイアウト切り替え [ 元のレイアウト ; アニメーション: なし ]
計算式はすべて、変数を設定
スクリプトステップ内で行います。
この計算をする際に、最初に作った自己リレーションシップが効きます。
あるレイアウトで計算した場合、選択されている現在のレコードを対象に計算するのが FileMaker の基本です。
それでは、1レコード分しか計算できませんので、現在のレコードの関連レコードを対象に計算を行う必要があります。
デカルト積は、一つのレコードに対してすべてのレコードが繋がるので、この場合、_PLAYERBATTING
レイアウトでどのレコードが選択されていようと、__playerbatting_PLAYERBATTING
のすべてのレコードが関連レコードとなり、129名分のデータを計算対象にできます。
まず、FileMaker Pro に用意されている関数で計算できるものを計算してしまいます。
変数を設定 [ $$LENGTH ; 値: Count ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$SUM ; 値: Sum ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$MEAN ; 値: Average ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$MAX ; 値: Max ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$MIN ; 値: Min ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$VAR ; 値: Variance ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$SD ; 値: StDev ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$VARP ; 値: VarianceP ( __playerbatting_PLAYERBATTING::batting_avg ) ]
変数を設定 [ $$SDP ; 値: StDevP ( __playerbatting_PLAYERBATTING::batting_avg ) ]
度数分布表、ヒストグラムの階級の作成に必要なデータの準備
まず、打率データの最大値から最小値までの範囲を計算しておきます。
変数を設定 [ $$RANGE ; 値: $$MAX - $$MIN ]
次に、スタージェスの公式を使って、この範囲の階級数を決めます。
k = log_2N +1
変数を設定 [ $NUMBER_OF_LEVELS ; 値: Round ( 1 + Lg ( $$LENGTH ) ; 1 ) ]
N はレコード数で、計算結果を四捨五入しています。
また、これはレイアウトには必要ないので、ローカル変数を使っています。
ここで、データビューアかカスタムダイアログを使って、Round ( 1 + Lg ( $$LENGTH ) ; 1 )
の結果を把握しておきます。一度、OnFirstWindowOpen
を実行すれば、$$LENGTH
には値が入っています。
結果は、8 となるはずです。
階級の幅と階級の数で階級を作成
範囲と、階級の数が決まったので、階級を作成します。
まず、間隔を決めます。これもローカル変数です。
変数を設定 [ $LEVEL_RANGE_STEP ; 値: Ceiling ( $$RANGE * 100 / $NUMBER_OF_LEVELS ) / 100 ]
範囲を階級数で割っているだけです。打率らしく見せるために、範囲を100倍して割って、それを切り上げた後、100で割って戻しています。
次に、階級の始点(正確には、始点を含まない「超」ということになります)を計算します。これもローカル変数です。
変数を設定 [ $LEVEL_RANGE_FROM ; 値: Ceiling ( $$MIN * 100 ) / 100 - $LEVEL_RANGE_STEP ]
最小値を含めばいいわけですから、間隔に準じて、最小値を100倍にして切り上げ後、100で割り、間隔分減算します。
次に、階級の終点(こちらは含む「以下」ということになります)を計算します。これもローカル変数です。
変数を設定 [ $LEVEL_RANGE_TO ; 値: Ceiling ( $$MAX * 100 ) / 100 + $LEVEL_RANGE_STEP ]
最大値を含むには、切り上げ計算後、間隔分加算しておけば含まれることになります。
階級の作成
階級の間隔、始点(含まない)、終点(含む)が決まったので、各ラベルを作成し、値一覧(Value List)にします。計算式は、別に書きます。これはグローバル変数です。
変数を設定 [ $$CLASSIFIED ; 値: ]
While (
[
~from = $LEVEL_RANGE_FROM ;
~to = $LEVEL_RANGE_TO ;
~step = $LEVEL_RANGE_STEP ;
~item = ""
] ;
~from < ~to ;
[
~item = GetAsText ( ~item ) & Left ( GetAsText ( ~from ) & "000" ; 4 ) & ¶ ;
~from = ~from + ~step
] ;
Left ( ~item ; Length ( ~item ) - 1 )
)
計算した、始点、終点、間隔を、変数 ~from
、~to
、~step
に代入します。ラベル用の ~item
を用意しておきます。
繰り返し処理を行なって、.000
形式で文字列を作成し、値一覧は改行区切りリストですから1個作成毎に改行 ¶
を追加します。
最後に、終端の改行を取り除いています。ここでは必須ではありませんが、値一覧同士を結合するケースもあるので、習慣として値一覧を作成したときは、最後の ¶
は取り除くということに統一しておいた方がいいと思います。
度数のカウント
次に度数をカウントします。これは、各打率データがどの階級に含まれるかをカウントするということです。
グローバル変数 $$FREQUENCY
にカウントした各階級の度数を値一覧として代入します。
変数を設定 [ $$FREQUENCY ; 値: ]
計算式です。
While (
[
~record_number = 1 ;
~length = $$LENGTH ;
~classified = $$CLASSIFIED ;
~current = "" ;
$COUNTER[1] = 0 ;
$COUNTER[2] = 0 ;
$COUNTER[3] = 0 ;
$COUNTER[4] = 0 ;
$COUNTER[5] = 0 ;
$COUNTER[6] = 0 ;
$COUNTER[7] = 0 ;
$COUNTER[8] = 0 ;
~index = ""
] ;
~record_number ≤ ~length ;
[
~current = GetAsNumber ( GetNthRecord ( __playerbatting_PLAYERBATTING::batting_avg ; ~record_number ) ) ;
~index = Case (
~current > ( GetAsNumber ( GetValue ( ~classified ; 1 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 2 ) ) ) ;
1;
~current > ( GetAsNumber ( GetValue ( ~classified ; 2 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 3 ) ) ) ;
2;
~current > ( GetAsNumber ( GetValue ( ~classified ; 3 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 4 ) ) ) ;
3;
~current > ( GetAsNumber ( GetValue ( ~classified ; 4 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 5 ) ) ) ;
4;
~current > ( GetAsNumber ( GetValue ( ~classified ; 5 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 6 ) ) ) ;
5;
~current > ( GetAsNumber ( GetValue ( ~classified ; 6 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 7 ) ) ) ;
6;
~current > ( GetAsNumber ( GetValue ( ~classified ; 7 ) ) ) and
~current ≤ ( GetAsNumber ( GetValue ( ~classified ; 8 ) ) ) ;
7;
8
) ;
$COUNTER[~index] = $COUNTER[~index] + 1 ;
~record_number = ~record_number + 1
] ;
$COUNTER[1] & ¶ &
$COUNTER[2] & ¶ &
$COUNTER[3] & ¶ &
$COUNTER[4] & ¶ &
$COUNTER[5] & ¶ &
$COUNTER[6] & ¶ &
$COUNTER[7] & ¶ &
$COUNTER[8]
)
階級数 8 とわかった上での計算式です。
レコードをレコード番号(変数 ~record_number
)で順番に参照していきます。最後のレコード番号は対象レコード数ですので、変数 ~length
に、グローバル変数 $$LENGTH
を代入します。カウントする際の評価のため、先ほど作った階級の値一覧が入ったグローバル変数 $$CLASSIFIED
を変数 ~classified
に代入して使っています。
ロジックは、GetNthRecord
関数で参照する関連レコード __playerbatting_PLAYERBATTING
の現在のレコード番号 ~record_number
のフィールド batting_avg
の値が、$$CLASSIFIED
のどの階級に含まれるかを評価して、下から順番に振った階級番号を変数 ~index
にセットします。
セットされら番号を数値インデックスとして、ローカル配列(繰り返し変数) $COUNTER[]
でカウントしていきます。
FileMaker Pro の配列は、1 から始まるので、1 から 8 までの ~index
によって、$COUNTER[]
がインクリメントされるということです。
最後のレコードが評価されると、各インデックスごとの $COUNTER[]
の値が決まっているので、これを順番通りに値一覧としています。
ここで一旦、保存します。
グラフの作成
FileMaker のグラフは、レイアウトオブジェクトとしてのグラフと JavaScript アドオンの簡易グラフがあります。今回は、レイアウトオブジェクトを使います。 JavaScript アドオンを使用したい場合は、グローバル変数ではなく、計算結果保存用のテーブルを作って設定することになります。
グラフを配置したら以下のように設定します。
項目 | 内容 |
---|---|
タイトル | "2024年 MLB 規定到達選手の打率のヒストグラム" |
タイプ | 縦棒グラフ |
X 軸 (水平): タイトル | "階級(打率)" |
X 軸 (水平): データ | $$CLASSIFIED |
Y 軸 (垂直): タイトル | "度数(人数)" |
X 軸 (水平): データ | $$FREQUENCY |
Y 軸: 目盛 | 線形 |
Y 軸: 補助目盛(小)を表示 | 5 |
Y 軸: 最小値を設定 | 0 |
Y 軸: 最大値を設定 | 55 |
項目 | 内容 |
---|---|
グラフデータ | 現在のレコード(区切りデータ) |
OnFirstWindowOpen
スクリプトを実行すると、縦棒グラフによるヒストグラムが表示されるはずです。
中央値の計算
FileMaker には中央値を求める関数はありませんので、自分で算出します。PlayerBattingStats
の続きになります。
レイアウト切り替え [ 「__playerbatting_PLAYERBATTING」 (__playerbatting_PLAYERBATTING) ; アニメーション: なし ]
レコードのソート [ 記憶する ; ダイアログあり: オフ ]
ソートの記憶内容は、__playerbatting_PLAYERBATTING::batting_avg
を降順ソートです。
続いて、中央値を計算し、グローバル変数 $$MEDIAN
に代入します。
変数を設定 [ $$MEDIAN ; 値: ]
計算式は以下の通りです。
Let ([
~is_odd = Mod ( $$LENGTH ; 2 )
] ;
Case (
~is_odd = 1 ;
GetNthRecord ( __playerbatting_PLAYERBATTING::batting_avg ; Ceiling ( $$LENGTH / 2 ) ) ;
~is_odd = 0 ;
Let ([
~temp = GetNthRecord ( __playerbatting_PLAYERBATTING::batting_avg ; Ceiling ( $$LENGTH / 2 ) ) ;
~temp = ( ~temp + GetNthRecord ( __playerbatting_PLAYERBATTING::batting_avg ; Truncate ( $$LENGTH / 2 ; 0 ) ) ) / 2
] ;
~temp
)
)
)
レコード数が奇数であるかを調べるため、レコード数の2の剰余をとり、奇数なら 1、偶数なら 0 がセットされます。
それを条件として、Case
関数を使って、処理を振り分けます。
奇数の場合は、2で割った値の切り上げで算出したレコード番号の打率を取得します。
偶数の場合、レコード数を2で割った値の切り捨てと切り上げて算出した2つのレコード番号を使って、打率を取得し平均を出します。
度数分布表を作成する
FileMaker Pro で、クロス集計等の表を作りたいときは、Web ビューアを使用して、HTML でテーブルを作成します。
まず、session
テーブルに表のデータ部分を除いた、HTML のコードを保存するグローバルフィールドを作成し、_SESSION
レイアウトに配置します。
名前 | タイプ |
---|---|
html_begin | テキスト |
html_end | テキスト |
今回は、Bootstrap の Card コンポーネントにテーブルを配置してみました。各グローバルフィールドの内容は以下の通りです。
data:text/html,
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2024 シーズン 打率</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row mt-3">
<div class="col">
<div class="card">
<div class="card-header">
2024 シーズン 打率 度数分布表
</div>
<div class="card-body">
<table class="table table-dark table-striped">
<thead>
<tr>
<th scope="row">度数</th>
<th scope="row">人数</th>
</tr>
</thead>
<tbody>
FileMaker 内で、HTML ソースコードを持つ場合は、データ HTML として扱います。
ソースコードの冒頭に data:text/html,
が必要になります。
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>
データ部分は、Web ビューアの計算式で作成します。
_PLAYERBATTING
レイアウトでレイアウトモードに移り、Web ビューアを配置します。
以下の計算式を、Web アドレスに設定します。
_SESSION::g_html_begin &
While (
[
~table_row_begin = "<tr><th>" ;
~table_data_begin = "</th><td>" ;
~table_row_end = "</td></tr>" ;
~table_data = "" ;
~value_count = ValueCount ( $$CLASSIFIED ) ;
~current_value = 1
] ;
~current_value < ~value_count ;
[
~table_data = ~table_data &
~table_row_begin &
GetValue ( $$CLASSIFIED ; ~current_value ) & " - " & GetValue ( $$CLASSIFIED ; ~current_value + 1 ) &
~table_data_begin &
GetAsText ( GetValue ( $$FREQUENCY ; ~current_value ) ) &
~table_row_end ;
~current_value = ~current_value + 1
] ;
~table_data
) &
_SESSION::g_html_end
階級と度数の値一覧から、一つづつデータを取り出し、テーブルの行を作っています。
これを階級数分だけ繰り返します。
この処理を、先ほど作ったグローバルフィールドに設定した開始と終了の HTML で挟みます。
起動時にスクリプトは実行されるので、起動すれば計算、グラフ描画、表作成すべて完了しますが、スクリプト実行用のボタンを付けても良いでしょう。
まとめ
私の体感ですが、計算フィールド、集計フィールド、Loop スクリプトステップは、パフォーマンスが目に見えて落ちる場合があります。
今回は、統計学の初歩の初歩を題材にしましたが、何かの参考になれば幸いです。