AdventCalendar
FileMaker
人工知能
集合
FileMakerDay 19

ファイルメーカーを使って考える集合の話(1)

AdventCalendarにも、Qiitaにも初めての投稿になります。お邪魔だったらごめんなさい。これをファイルメーカーの話題に含めてもよいのかどうか、自分でも疑問ですが、@tyumaさんの呼びかけに心動かされ、どこかで誰かの役に立てば、という気持ちでの投稿です。

あらかじめ断っておきますが、すでにpython+tableauを操ってビジュアライズされたグラフをサクサク作成できるような方には全くくだらない記事かもしれません。(筆者はそのあたりの技術には疎く、手も足も出ません)



人間は3つ以上の集合が苦手

さまざまな場面において、データの重複を管理したい、という要求はあると思います。

ファイルメーカーにおいても、重複した入力を制限する機能を使ったり、既に入力されたデータのうち重複を洗い出して処理したりする、ということは日常的にあるのではないでしょうか?

たとえば自分の話ですが、機械学習における教師データを扱うときなど、いろいろと悩まされることが多いです。そして、個人的には、この「重複をどのように取り扱うか」における考え方や処理は、機械学習やAIを使うにあたって、「つまずきやすく、意外と手間取ってしまうポイント」の1つだと思っています。

なぜかというと、人間は3つ以上の集合を扱うのがとても苦手だからです。ファイルメーカーの話をする前に、ちょっとだけ、集合の話にお付き合いください。


2つの集合なら簡単

集合Aと集合Bの2つしかない場合は、「Aである」、「Bである」、「AかつBである」の3種類しかないので、わりと直感的に判断ができます。

ある人のもつ属性を集合としてとらえ、ベン図で表すと、次のようになります。

Venn's_two_ellipse_construction.jpg

【図 2つの集合のベン図】

A:東京都在住

B:男性

この場合、言葉で表現すると、

A:「東京都在住」

B:「男性」

A&B:「東京都在住の男性」

の3つに分かれます。


3つの集合は?

集合が3つの場合はどうでしょうか?

A:東京都在住

B:男性

C:30歳以上

Venn's_three_ellipse_construction.jpg

【図 3つの集合のベン図】

ベン図はこのようになり、言葉で表現すると、

A:「東京都在住で、男性ではなく、30歳未満」

B:「東京都在住ではなく、男性で、30歳未満」

C:「東京都在住ではなく、男性ではなく、30歳以上」

A&B:「東京都在住の男性で、30歳未満」

A&C:「東京都在住で、男性ではなく、30歳以上」

B&C:「東京都在住ではなく、男性で、30歳以上」

A&B&C:「東京都在住の男性で、30歳以上」

このように部分集合は7種類になります。ちょっと複雑になり、直感的にわかりにくくなったと思いませんか?

わかりにくいけれど、このくらいならまだ何とかなります。


4つの集合だと、もう無理!

では、集合が4つになったらどうなるでしょうか?

A:東京都在住

B:男性

C:30歳以上

D:年収500万円以上

A:「東京都在住で、男性ではなく、30歳未満で、年収500万円未満」

B:「東京都在住ではなく、男性で、30歳未満で、年収500万円未満」

C:「東京都在住でなく、男性ではなく、30歳以上で、年収500万円未満」

D:「東京都在住でなく、男性ではなく、30歳未満で、年収500万円以上」

A&B:「東京都在住の男性で、30歳未満で、年収500万円未満」

A&C:「東京都在住で、男性ではなく、30歳以上で、年収500万円未満」

B&C:「東京都在住ではなく、男性で、30歳以上で、年収500万円未満」

A&D:「東京都在住で、男性ではなく、30歳未満で、年収500万円以上」

B&D:「東京都在住ではなく、男性で、30歳未満で、年収500万円以上」

C&D:「東京都在住ではなく、男性ではなく、30才以上で、年収500万円以上」

A&B&C:「東京都在住の男性で、30歳以上で、年収500万円未満」

A&C&D:「東京都在住で、男性ではなく、30才以上で、年収500万円以上」

B&C&D:「東京都在住ではなく、男性で、30才以上で、年収500万円以上」

A&B&D:「東京都在住の男性で、30歳未満で、年収500万円以上」

A&B&C&D:「東京都在住の男性で、30歳以上で、年収500万円以上」

部分集合は実に15種類にもおよび、ベン図は下のようになります。

Venn's_four_ellipse_construction.jpg

【図 4つの集合のベン図】

相手が1人だとしても、頭の中で即座にこのベン図を描き、その人がどの部分に属するかをパッと思い浮かべられる、などという能力の持ち主はほとんどいないのではないでしょうか? 相手が1人ならまだしも、対象が数千人、数万人規模で、コンピューターなしでこれをやろうとするのは人間の能力を超えているのは明らかでしょう。

また、ここでは4つの条件を and で重ねていますが、このうちの1つでも or 条件だったらどうでしょうか?

「東京都在住で、男性、または、30才以上で、年収500万円以上」

「東京都在住、または、男性ではなく、30歳未満で、年収500万円以上」

さらにイメージしづらくなったのではないでしょうか? 「または」が文章のどこまでにかかるのか、を言葉だけで表すのは困難であり、これを正確に表すには数学の集合記号(A∩BやA∪Bなど)を使う必要があります。

つまり、条件を表す言葉を聞いても、集合とその部分集合をうまくイメージできないのは、人間の能力の限界であると同時に、言葉の限界でもあるのです。

このように、「一般的な人間が頭の中でパッとイメージできる集合は、せいぜい3つまでで、and条件のみ」である、というのが私の持論です。(統計学的な調査をしたわけではありませんが、実験すればだいたいこのぐらいになるのではないでしょうか)

しかし、コンピューターは条件がいくつあっても、andとorが複雑に混在していても、いったん記憶させた条件は忘れることなく覚えておくことができます。苦手な部分こそ、コンピューターに頼ってしまいましょう。


ファイルメーカーで集合を扱ってみる

さて、やっとファイルメーカーの出番です。ファイルメーカーはデータをビジュアライズする方法が豊富で、実装も簡単です。

ここでは、ごく簡単なサンプルを用意し、それらを集合として扱ってみます。

先にファイルの動作だけを説明して、それからファイルのしくみを説明します。


3つの集合を分析して概観を得る

サンプルとして名簿を取り上げます。想定としては、このデータはファイルメーカーを使って入力されたものではなく、A, B, Cが別々の場所で入力され、自分の手元に送られてきた単なるテキストファイル、という設定です。(いわゆる「名寄せ」です。今回は便宜上、同姓同名の人はいないものとして扱います)

そのテキストファイルからドラッグ&ドロップ(またはペースト)されたリストA, B, Cがあり、どうやらこの中には重複がありそうです。しかし、「Aにあって、Bにはなくて、Cにもなくて」、と1つ1つ確認するのは手間がかかりすぎます。

そこで「Go」ボタンを押すと、

cap_ABC_List.png

【図 リストA, B,C】

cap_Analyze_ABC.png

このように、それぞれの人名が、「Aだけに出現する」、「Bだけに出現する」、「Cだけに出現する」、「AとBに出現する」、「AとCに出現する」、「BとCに出現する」、「ABC全てに出現する」、に色分けして分類・カウントしてくれます。

(色で判別しやすいように、A=赤、B=青、C=黄色、とし、A&B=赤と青=紫、A&C=赤と黄色=オレンジ、B&C=青と黄色=緑、A&B&C=グレー、に設定してあります。)

さらに、ヘッダ右端にある円グラフボタンを押すと、カードウインドウが開いて、ベン図と、比率をグラフ化して見られます。

なお、ベン図から引き出された数字をクリックすると、その集合(A&BならA&B)だけが検索されて絞り込まれます。

cap_CardWindow.png

【図 ベン図と円グラフのカードウインドウ】

・Backボタンで、テキストをドラッグ&ドロップ(またはペースト)する画面に戻ります。

こんなふうに、集合の中身をざっくり概観して傾向をつかみ、「さて、これからどうしよう」と処理の方針を立てるときの、いわば前処理用ファイルです。(ゆえに、このファイルはレコードの長期的な格納を目的としていません)


ファイルのしくみ


テーブル一覧

テーブルは全部で4つ、テーブルA〜Cと、グローバルテーブルです。

cap_All_Table.png

【図 テーブル構成】


フィールド定義

テーブルA〜Cは、同じ構造で、IDと単語とExist(リレーション用。ここには、計算値自動入力で常に1が入力されます)

cap_TableA.png

【図 フィールド定義:テーブルA】

cap_TableB.png

【図 フィールド定義:テーブルB】

cap_TableC.png

【図 フィールド定義:テーブルC】

cap_TableGrobal.png

【図 フィールド定義:グローバルテーブル】

グローバルテーブルは、フィールド数は多いですが、ほとんどはカウント用のグローバルフィールドです。

少し説明が必要なのは、LookUpA〜Cのところでしょうか? リレーションシップグラフを見るとわかるのですが、テーブルAにある単語(人名)であれば、自動的に1が入力されます。

「集合分類」の計算式が途切れてしまっていますので書いておきます。

Case ( LookUp_A = 1 and LookUp_B = 1 and LookUp_C = 1 ; "A&B&C" ;

LookUp_A = 1 and LookUp_B = 1 and LookUp_C = "" ; "A&B" ;
LookUp_A = "" and LookUp_B = 1 and LookUp_C = 1 ; "B&C" ;
LookUp_A = 1 and LookUp_B = "" and LookUp_C = 1 ; "A&C" ;
LookUp_A = 1 and LookUp_B = "" and LookUp_C = "" ; "A" ;
LookUp_A = "" and LookUp_B = 1 and LookUp_C = "" ; "B" ;
LookUp_A = "" and LookUp_B = "" and LookUp_C = 1 ; "C" ;
"" )

LookUpA〜Cのどれに1が入っているかで、どれに分類されるかを判別しています。


リレーションシップ

リレーションシップグラフは以下のようになっています。

cap_Relation.png

【図 リレーションシップグラフ】

かいつまんで処理の流れを説明しておくと、


  1. ドラッグ&ドロップされた改行区切りテキストを一行ずつ値として切り出し、AならテーブルAに1レコード作ってそこに入れる。Existに1が入る。

  2. テーブルB, Cにも同じ処理を行う。


  3. グローバルテーブルにもレコードを作って同じ値を入れる。リレーションが発生するので、テーブルAにある値であればLookUp_Aに1が入る。同様にLookUp_B, LookUP_Cの値を使って、どの部分集合に属するかを判別する。




スクリプト

念のためスクリプトも貼っておきます。

スクリプト:Goボタン_ドロップされたテキストを分析・種類分け

# グローバルテーブルの全レコードを削除してから処理を開始

レイアウト切り替え [ 「グローバルテーブル」 (グローバルテーブル) ; アニメーション: なし ]
テーブルデータを削除 [ ダイアログあり: オフ ; テーブル: <現在のテーブル> ]
#
変数を設定 [ $TableName ; 値: "A" ]
#
# A,B,Cを切り替えるループ
Loop
変数を設定 [ $DropFieldName ; 値: "グローバルテーブル::g_DropTo" & $TableName ]
変数を設定 [ $TextAll ; 値: GetField ( $DropFieldName ) ]
#
# 値の数を取得(空行は削除)
# 同一テーブル内での重複を省く
#
変数を設定 [ $NumOfValues ; 値: ValueCount ( Substitute ( $TextAll ; "¶¶" ; "¶" )) ]
変数を設定 [ $NumFieldName ; 値: "グローバルテーブル::" & $TableName & "_ValueCount" ]
フィールドを名前で設定 [ $NumFieldName ; $NumOfValues ]
#
フィールドを名前で設定 [ $FieldName ; Evaluate ( "TextStyleRemove ( グローバルテーブル::g_DropTo" & $TableName & ";すべてのスタイル )" ) ]
#
# ABC各テーブル、全レコードを削除してから処理を開始
#
レイアウト切り替え [ "テーブル" & $TableName ; アニメーション: なし ]
# 前回までのレコードを削除
テーブルデータを削除 [ ダイアログあり: オフ ; テーブル: <現在のテーブル> ]
#
変数を設定 [ $j ; 値: 1 ]
# ABC各テーブルにレコードを作るループ
Loop
変数を設定 [ $CurrentValue ; 値: MiddleValues ( $TextAll ; $j ; 1 ) ]
# 空行およびスペースのみの行の場合はレコードを作らない。(1文字でもあれば作る)
If [ Substitute ( Trim ( $CurrentValue ) ; "¶" ; "" ) ≠ "" ]
新規レコード/検索条件
フィールドを名前で設定 [ "テーブル" & $TableName & "::" & "単語" ; $CurrentValue ]
End If
変数を設定 [ $j ; 値: $j+1 ]
Exit Loop If [ $j>$NumOfValues ]
End Loop
#
# テーブルCまでやったら終了
Exit Loop If [ $TableName="C" ]
#
# テーブルを切り替え
変数を設定 [ $TableName ; 値: Case ( $TableName="" ; "A" ; $TableName="A" ; "B" ; $TableName="B" ; "C" ; "" ) ]
End Loop
#
# グローバルテーブルにレコードを作る
#
レイアウト切り替え [ 「グローバルテーブル」 (グローバルテーブル) ; アニメーション: なし ]
変数を設定 [ $All_Unique_Values ; 値: UniqueValues ( List ( グローバルテーブル::g_DropToA ; グローバルテーブル::g_DropToB ; グローバルテーブル::g_DropToC ) ; 1 ) /*Listで取得した3つのフィールドを、UniqueValuesで重複を省く*/ ]
変数を設定 [ $NumOfValues ; 値: ValueCount ( $All_Unique_Values) ]
#
変数を設定 [ $j ; 値: 1 ]
Loop
新規レコード/検索条件
変数を設定 [ $CurrentValue ; 値: MiddleValues ( $All_Unique_Values ; $j ; 1 ) ]
フィールド設定 [ グローバルテーブル::単語 ; $CurrentValue ]
変数を設定 [ $j ; 値: $j+1 ]
Exit Loop If [ $j>$NumOfValues ]
End Loop
#
レコードのソート [ 記憶する ; ダイアログあり: オフ ]
フィールドへ移動 []



サンプルファイルのダウンロード

3つの集合for公開用サンプル

4つ以上の集合の場合も、フィールドを増やせば同様のことができます。サンプルを元に、自分で試してみてください。

すみませんが、この話、まだ続きがあります。

次回へ続く)