JMP 19では、JMP 18で導入されたデータコネクター機能が拡張され、より多くのデータソースへの統一的なアクセスが可能になります。組み込み接続タイプが追加されるだけでなく、JMP 19では、公開されたPython APIを使用してユーザーが独自の接続タイプを定義できるようになります。組み込みタイプと同様に、これらのタイプもデータコネクター機能の利点(設定管理やクエリビルダーとの統合など)を共有できます。また、Pythonで実装されているため、既存のPythonライブラリを利用できます。このプレゼンテーションでは、APIについて説明し、その概念を説明し、実際の動作を紹介します。
こんにちは。本日はJMPのデータコネクタフレームワークについて、特にJMP 19でPythonを使ってどのように拡張できるかについてお話しします。JMP 18ではデータコネクタフレームワークを導入し、ODBCとの接続性を刷新しました。JMP 19では、これをさらに拡張します。
SaaSへの再接続やSnowflakeへのネイティブ接続の提供にこのフレームワークをどのように活用しているかについては、すでに他の場所でご紹介したことがあるかもしれません。また、これからご紹介するこのフレームワーク、つまりPython APIを使って、Amazon S3とMicrosoft SharePointへの接続を実装しています。これらの機能はJMPにはバンドルされていませんが、JMP Marketplaceを通じて配布される予定です。
この講演ではいくつかお話ししたいことがあります。まず、ご自身のデータソースをお持ちの場合、なぜJMPと統合する必要があるのか、その理由を簡単にご説明したいと思います。JMPと統合する場合は、JMPがどのような機能を持つのか、JMPがデータソースと連携するために何が必要なのか、そしてそのために何を用意する必要があるのかを知っておく必要があります。特に、どのようなコードを記述する必要があるのかをご理解ください。
最後に、共有方法についてお話しします。ご自身で作成したコードはご自身で利用されているかもしれませんが、社内の他の人もそれを使いたいかもしれません。世界中のJMPユーザーと共有したい場合もあるでしょう。これを容易にするために、私たちはPythonを選択しました。これは、Python用のライブラリが既に利用可能であること、あるいはご自身で作成したコードが利用できることが理由の一つです。Amazon S3とMicrosoft SharePointへの接続も、この方法で実現しました。既存のPythonライブラリをラップすることで、皆様にとってもより使いやすくなることを願っています。
シンプルさを追求しただけでなく、統合をお勧めするもう一つの理由は、設定を管理できる点です。ユーザーはコネクタエディタやクエリビルダーを使用して、接続を記述する設定を作成・編集できます。クエリビルダーは、グラフィカルな画面でクエリを作成したり、データをフィルタリングしたり、列を選択したりといった様々な操作が可能です。そして、共同作業も可能です。作成した接続と設定は、アドインを介してエクスポート・共有できます。
この機能についてまだご覧になっていない方は、後ほど実際に動作をお見せする際に詳しくご説明します。その前に、各機能がどこにどのように組み合わさるのか、そしてどのように連携するのかについてお話ししましょう。まず、データソースを用意し、それをJMPと連携させます。
JMPのデータといえば、データテーブルについてです。これらのテーブルは、データソースから簡単に提供できるものもあれば、実現するために多少の作業が必要なものもあります。例えば、階層構造のJSONデータを返すWebエンドポイントがあるとします。その場合、何らかの処理が必要になります。しかし、JMPのデータテーブルとして公開できる限り、利用できる可能性があります。
クエリビルダーは、データにアクセスする際、MySQLやSQL Serverのような従来のデータベースのように扱います。つまり、データが名前付きテーブルに整理されており、それらのテーブルがさらに名前付きスキーマに整理されていることを前提としています。
しかし、おそらく複数のデータソースがあるでしょう。あるいは、そうでなくても、少なくとも複数のアクセス方法があります。ユーザーによって認証情報が異なるかもしれませんし、データの保存場所のアドレスをハードコードしたくない場合もあるでしょう。何らかの形で、オプションが用意されているはずです。これらのオプションに特定の値を指定することで、構成が作成されます。そして、その構成を使ってデータソースを作成できます。これらの構成は、データコネクタエディタで作成できます。
これが概要です。では、これをどのようにコードに変換するのでしょうか?見てみましょう。私はJMPを操作しています。このウィンドウを一旦閉じて、最初からやり直しましょう。左側には、いくつかのデータコネクタタイプを実装するコードがあります。タイプとは、JMPに組み込まれているODBCのようなものです。SaaSタイプ(ローカル、リモート、または接続経由)のいずれかである可能性があります。Snowflakeかもしれません。いずれにしても、実装する特定のタイプであれば、Pythonも選択肢の1つです。
上部にはインポートがいくつかあります。特に、組み込みのJMPモジュールのインポートがあります。ここには、関数が呼び出されたタイミングを確認するためのユーティリティ関数があります。このデモでは、JMPがコードを呼び出すため、この関数を使用しています。ここではコードをあまり呼び出さないので、何が起こっているかを把握することが重要です。
この下のコードが、実際に型を実装するものです。詳しく説明する前に、このリファレンスを確認しましょう。このリストにある様々な項目は、多くの点で、先ほどお見せした図に対応しています。オプションが必要です。そのためには、このクラスjmp.DataConnectorTypeをサブクラス化し、それらのオプションを定義するフィールド属性を定義します。データソースに変換する必要がある設定があります。jmp.DataConnectorをサブクラス化し、その変換を実行する関数_do_as_data_sourceを定義します。
最後に、データソース自体があります。これもまたサブクラスです。スキーマが何であるか(存在する場合)、テーブルが何であるか、特定のテーブルのデータが何であるかを知るための関数を定義します。デモでは見られませんが、覚えておくと便利な点があります。例えば、自動的にクリーンアップされないリソースがある場合、例えば、この動作がまだ実装されていない接続がある場合などです。そのような場合は、参照されなくなったリソースをクリーンアップできるPythonの特別なダブルアンダースコアメソッドを見つける必要があります。JMPはデータソースを閉じる際に、Pythonにクリーンアップを任せるという方法でデータソースをクローズするためです。
これを念頭に置いて、もう一度コードを見てみましょう。jmp.DataConnectorType のサブクラスである SimpleFolderConnectorType があります。前述の通り、このクラスにはオプションを持つフィールドがあります。ここでの最初で唯一のオプションは「Folder」です。
この場合、フォルダとは、つまりSimpleFolder接続がどのようになるかということですが、データソースが存在することを意味します。これはローカルファイルシステム上の単一のフォルダに対応し、そのフォルダはその中にあるファイルの形式でデータを公開します。
まず、「Folder」という変数があり、オプションを表すこの辞書の値はstr | None型です。これは、この任意の文字列値を持つことを意味します。次に、jmp.DataConnectorのサブクラスである設定クラスSimpleFolderConnectorがあります。このクラスは、設定からデータを変換、あるいは接続する_do_as_data_source関数を定義します。
設定値「Folder」には、組み込み辞書と同じように、self をインデックスとして参照するだけでアクセスできます。値が「none」の場合、つまりユーザーがデフォルト値を省略した場合には、エラーをスローして設定が必要であることをユーザーに知らせます。
最後に、データソースに変換します。データソースクラスは、このリストの最後です。もちろん、作成できる必要があります。init関数を定義します。今回の場合、実際にディレクトリであるかどうかを確認します。これは、ユーザー、自分自身、またはコードを使用している人にエラーがあれば知らせたいためです。データソースを作成する際は、接続時に行う必要があります。今回の場合、実際には接続を行う必要はありませんが、少なくともフォルダが存在するかどうかを確認できます。
次に挙げるのは get-schemas です。この場合、データソースにはスキーマが存在しないという前提で話を進めます。テーブルに直接アクセスできるだけです。get-schemas を定義する必要はなく、JMP はデータソースがスキーマを使用していないことを認識します。
ただし、テーブルが何であるかを定義する必要があります。今回の場合、スキーマパラメータを取る必要がありますが、使用しないので無視します。ただし、ファイルを参照しているので、何らかの方法でファイルに名前を付けます。今回の場合は、名前だけです。フォルダを反復処理し、フォルダ内のすべてのファイルの名前を返します。
最後に、データテーブルを取得する必要があります。そのために、この最後の open table 関数を使用します。まず、データが格納されているファイルのパスを再構成する必要があります。テーブル名(ファイル名とフォルダ名を付加したもの)を取得し、パスを文字列として返します。
先ほどお話ししたモデルではデータテーブルを返すように要求されていますが、これは可能です。先ほど例を挙げたように、Webエンドポイントがある場合は、階層構造のJSONなどが返されます。おそらく、これを自分でデータテーブルに変換したり、直接返したりする必要があるでしょう。
しかし、今回のケースはファイルシステムからのアクセスであり、S3およびSharePointアドインの作成で見られたケースであり、他のファイル型データストアへのアクセスに関しても関心が寄せられていると思いますが、ファイルを直接開くのではなく、この文字列を渡すだけで開くことができます。そうすると、JMPが自動的にファイルを開きます。
ファイルがプライベートに開かれていることを確認するなどの処理を行います。ここで返されるデータテーブルは実装の詳細であり、ユーザーに直接公開されるべきではありません。クエリビルダーを使用する場合、中間テーブルは表示されず、最終結果のみが表示されます。この場合、JMPがその処理を行います。この方法により、JMPはテーブルを開く際に必要な設定をすべて保持できます。これについては後ほど詳しく説明します。
最後に、関数 jmp.DataConnector.tie の呼び出しがあります。これは、2つの引数、つまり型クラスとコネクタクラスを受け取り、それらが互いを認識していることを確認しています。現時点ではどちらも互いを参照していませんが、JMP は上部にある DataConnectorType クラスをアクセスポイントとして使用します。型とは、接続を構成するすべてのコードと定義を包括的に捉えたものです。つまり、それらは互いを認識している必要があるのです。
最後に、最終的な型文字列が何であるかを示すコメントがあります。DBCの場合、あるいはODBCの場合です。SAS ViyaやSAS Remoteの場合もあります。この場合、Pythonでは、これは関数型であることを示すPythonコロンです。__main__はモジュール名です。これはJMPから直接実行したためです。最後に、SimpleFolderConnectorTypeはクラス名です。
実際に動作を見てみましょう。データコネクタダイアログに移動して新しい接続を作成すると、データコネクタエディタが開きます。タイプをPythonに変更すると、残りの文字列を入力できるようになります。入力してください。
これを実行すると、「Folder」オプションが表示されます。値が空なので、None に相当します。テストして接続を試みると、この関数とこの関数を呼び出そうとしますが、エラーメッセージが表示されます。「Folder が必要です」。
さて、実際にデータにアクセスするために、JMPに付属のサンプルインポートデータを使います。ここにあるフォルダです。コピーして、もう一度表示します。パスをコピーして、JMPで開いて、ここにコピーします。テストした内容が確認できます。これですべて正常ですので、接続してみましょう。
接続に成功しました。ここにテーブルのリストが表示されています。実は、これらのテーブルはここにあるファイルに対応しています。ここにAnimals_L.txtがあり、ここにも同じファイルがあります。ログを見ると、このログエントリ関数が役に立ったことがわかります。_do_as_data_sourceを複数回呼び出したこと、そしてテスト中にデータソースのinitを複数回呼び出したことが分かります。また、get-tablesが呼び出され、このリストが再び提供されていることも分かります。
ここでクエリビルダーを呼び出すと、列の内容にアクセスできることがわかります。クエリを作成していくと、列を追加できます。ここでフィルタリングも可能です。例えば、これらの季節のうち2つだけを選択することもできます。SQLを確認すると、これらすべてが取り込まれていることがわかります。クエリを実行すると、結果が表示されます。
これがどのように動作するのか不思議に思うかもしれません。SQLクエリを作成し、SQLコードを実行します。しかし、以前定義していた頃はSQLという概念はほとんどありませんでした。これが機能するのは、このopen table呼び出し、つまりデータを取得する方法を提供しているからです。JMPが残りの作業をすべてやってくれます。SQLiteという一種の組み込みデータベースを内部で使用し、保有するデータを公開してSQLコードを実行できるようにします。例えば、季節を2つだけ表示するwhere句などです。
もう少し複雑なファイルを見てみましょう。お気に入りのExcelクラスに戻りましょう。もう一度クエリビルダーを開いてください。実際に何かを行う前に、このダイアログが表示されます。ファイルがある場合は、[inaudible 00:17:43]回、実際に何が含まれているかを指定するためにプロンプトを表示する必要があると判断しました。JMPがこのファイルを開こうとすると、この場合はインポートウィザードで必要な情報を入力するように求められます。
すべて問題なさそうですので、「インポート」をクリックしてください。先ほどと同じように、列名とスナップショットが表示されます。「インポート」をクリックします。データがインポートされました。毎回この手順を踏まなければならないのかと疑問に思う方もいるかもしれません。答えは「いいえ」です。
このソーススクリプトを見てみましょう。一番上には、期待通りの動作が確認できます。「新しいSQLクエリ」があります。接続情報があり、この場合はデータコネクタによって提供されます。これが私たちの型を参照していること、そして指定したフォルダが含まれていることがわかります。
しかし、テーブルの詳細を見てみると、ここに「Open Settings」という新しいオプションがあることがわかります。これは、インポートウィザードで指定された内容を取得します。具体的には、基盤となる大きなクラステーブルのソーススクリプトで open に渡された追加の引数を取得し、ここに提供します。
こうすれば、先に進んでもう一度実行すると、すべてが開いていて、問題なさそうです。もし設定で何か標準的でない操作をしていた場合、例えば後の列から開始する必要があったとしても、それはここに反映されているはずです。ログを見ると、すべてが最初からやり直されたことがわかります。_do_as_data_source を再度呼び出したことがわかります。その後、テーブルのリストを取得し、実際にこのテーブルを再度開きました。
これが基本です。こちらは少し複雑に見えますが、実際には複雑なだけでなく、より充実した機能も備えています。繰り返しになりますが、これは接続を確保できる最低限の機能です。
見た目が少し洗練されましたね。シンプルなフォルダコネクタではなく、本格的なフォルダコネクタになります。フィールド定義は以前よりずっと複雑になっていますが、以前とほぼ同じ構成要素で構成されています。
この場合、まず最初に追加されるのは、jmp.DataConnectoGroupedFieldsの使用です。これにより、フィールドが異なるグループに属することを指定できるようになります。これは、データコネクタエディタでの表示方法に影響します。ODBCの場合、オプションは異なるグループにグループ化されており、Pythonでも同じ機能を使用できます。
各グループは、jmp.DataConnectorGroupedFieldsに渡されるリスト内のペアを指定します。名前があり、それから先ほどと同様に、オプション名とフィールド定義を持つ辞書があります。フィールド定義には、str | Noneという型が残っています。また、jmp.DataConnectorFieldを使用してラップしています。これは何度か使用しており、少し長いため、エイリアスを付けています。これは、例えばデータコネクタエディタでオプション名にマウスポインタを置いたときに表示されるツールチップなど、追加情報を指定するためです。
デフォルト値を指定します。また、データコネクタエディタに表示される名前を、JSLで使用される設定ファイルの名前とは異なる名前で指定することもできます。JMPでは、例えばODBCなどでこの機能を使用して、オプション名をローカライズしたり、JSLでデフォルトで使用されている文字列よりも少し分かりやすい文字列を提供したりしています。そのため、JSL形式の名前が必要です。
ここでの「Ordner」は、確かドイツ語で「フォルダ」という意味だと思います。その説明は後ほど説明します。データソースの表示方法の変更点についてですが、引き続きフォルダを基準としていますが、オプションでスキーマもサポートされるようになりました。これにより、従来のようにデータを表示するのではなく、ここで指定したフォルダの子フォルダをスキーマと見なし、そのフォルダ内のファイルをデータテーブルと見なすという新しい方法でデータを表示するオプションが提供されます。
特定の拡張子に制限し、それを切り取ることによって、テーブル名から基礎となるファイルへのマッピングの表示方法を変える別のオプションもあります。
接続時のデータソースへの変換も同様ですが、オプションが多いため少し複雑になります。「フォルダー」は引き続きチェックしますが、この拡張子のオプションもチェックします。エラーを発生させ、すべてのデータを新しいデータソースに渡します。
新しいデータソースは、事前にフォルダをチェックし、その他のオプションも設定します。スキーマも提供されます。スキーマを使用する場合は、フォルダ内のフォルダを参照する、似たようなコードになります。ディレクトリ内の子フォルダにはそれぞれ名前が付けられるので、それをスキーマ名として使用します。
ただし、スキーマを使用していない場合、つまり新しい高度なオプションを設定していない場合は、None を返します。以前はスキーマ(get-schemas)を定義していませんでした。しかし、ここではオプションで関数を定義することはできないため、代わりに「スキーマがありません」という意味で None を返すことができます。スキーマは使用していません。
Get-tablesは以前と似たような考え方です。最終的には、多数のファイルの名前を取得します。しかし、オプションの都合上、取得方法はいくつか考えられます。まずディレクトリを取得します。これはスキーマの種類によって異なります。実際、スキーマを使用しているかどうかによっても異なります。次に、ファイル名を取得する前に、ファイルに絞り込みます。拡張子を処理する場合は、より高度な処理を行います。
最後に、データテーブルを取得するために、get-schemasとget-tablesのプロセスを逆に実行します。パスを再構築する必要があります。拡張子を再度追加する場合もありますが、関連するディレクトリは取得されないため、ファイル名を最後に追加してから、パスとして返します。パスを再び文字列として返します。これらを結び付けて、前と同じように型名を取得します。
見てみましょう。参照できるように、ここでスクロールダウンします。前回と同様に、ここでタイプを設定しましょう。カスタマイズした名前とツールチップが表示されています。フォルダについては前回と同じものを使用します。パスを取得しましょう。実は、一つ上のパスに移動しましょう。スキーマ機能を使って、コピーします。
次に、子フォルダをスキーマとして使用することを指定しましょう。せっかくこの便利なオプションを追加したので、これを使用します。すべて正常に表示されているか確認しましょう。はい、接続できます。すべてのウィンドウが最前面中央に表示されていることを確認しましょう。
左側にスキーマが表示されています。これは実際にはフォルダ名です。これがインポートデータです。.txtファイルのみに制限されているのがわかります。先ほどの動物の例を見てみましょう。開いてみてください。データがあります。すべて正常に動作しています。先ほどと同じように呼び出しがここにあります。ログを見ると、get-schemas、get-tables、ユーティリティ関数、そしてopen tableの呼び出しがあります。
これらの型を作成するために、ここまでの作業をすべて行いました。さて、それを他の人と共有したいとお考えですか?最後に、その方法についてお話しします。もう一つチェックリストがあります。
Pythonコードは既に作成済みですが、その書き方によっては、パッケージとして構造化したい場合もあるでしょう。パッケージ化の目的は、コードをまとめてpipなどのツール(この場合はJMPでラップされたjpip)でインストールできるようにし、インストール時に依存関係を指定できるようにすることです。例えばS3はPyPIにあるBoto3パッケージに依存していますが、パッケージを使用することで、非常に自然な方法で依存関係を指定できます。
基本設定を作成する必要があります。先ほど、データコネクタエディタでタイプ名を直接入力したことを覚えていますか?これはこれで良いのですが、設定リストから何かを見つけられたらもっと簡単です。名前全体を覚える必要がなく、それに基づいて新しいものを作成できるのです。
おそらくインストールスクリプトを書いたくなるでしょう。素晴らしいPythonパッケージはありますが、実際に使える場所に置く必要があります。もしそれを自動化できれば、とても便利ですよね。
最後に、これらすべてをアドインにまとめたいと思います。アドインがあれば、簡単にインストールでき、マーケットプレイスで配布できます。いずれにせよ、これらすべてが1つのパッケージファイルにまとめられています。
では、どのようになっているか見てみましょう。ここに、先ほどご覧いただいたコードのさらに発展させたバージョンを含むアドインがあります。中身を確認できるよう、ここで解凍しました。リストの一番上にはPythonパッケージがあったと思います。これはここで作成しました。Pythonコードはありますが、これはPythonプロジェクト…いや、Pythonパッケージに構造化されています。pyproject.tomlファイルがあります。今日は具体的な構築方法を説明する時間がありませんが、オンラインリソースが利用可能です。
Data Connectorsフォルダがあります。ここにはアドオンに付随する設定、つまりこの場合は基本設定が提供されています。例えば、このフォルダの1つを見てみると、タイプを指定する行が1行だけあることがわかります。これは追加の設定を指定しているわけではありませんが、ユーザーが参照できるものを提供しています。次に、この場合はaddinLoadがあり、ここにインストールスクリプトが格納されます。
もちろん、これらすべてを直接作成することもできますが、JMPのアドインクリエーターを使うこともできます。「ファイル」→「新規」→「アドイン」と進むと…名前とIDを指定できます。JMPの最低バージョンも指定できます。今回の場合は19です。「スタートアップスクリプト」には、パッケージをインストールするスクリプトを記述します。インストールは一度だけ行うようにします。
追加したいファイルは揃っています。今回は既にここに解凍されていますが、初めて作成する場合は、別の場所にあるかもしれません。パッケージを追加できます。これで必要なファイルがすべて揃いました。データコネクタも追加できます。サブスクリプトがあれば、実際に保存して独自のアドインを作成できるようになります。必要なファイルが揃っていれば、作成プロセスはかなり簡単になるはずです。
ここでお見せしたかったのは以上です。まとめましょう。まず最初に、なぜ統合が必要なのかについてお話ししたいと思います。冒頭でも触れましたが、データコネクタフレームワークには、設定の編集、共有、クエリビルダーを使ったインタラクティブなクエリ作成など、多くの機能が用意されていることをご理解いただければ幸いです。そして、それがデータソースをラップする良い理由となります。まさにこの点が、データソースをラップする上で重要なのです。JMPではすべてがデータテーブルであり、それをデータベースのように扱う必要があることを説明しました。
どのようなコードを書けばいいのか?定義したい様々なサブクラス、記述する必要がある様々な関数、オプションの指定方法について説明しました。最後に、アドインを作成し、作成したコードを他のユーザーと共有したり、JMPマーケットプレイスで公開したりする方法について説明しました。
これは、ある意味、可能なことのすべてを比較的簡単に概観したものです。皆さんの中には、しっかりとメモを取り、たくさんのスクリーンショットや写真を撮っている方もいるかもしれませんが、私のように、何か別の参照資料が欲しいと思う方もいるでしょう。幸いなことに、私たちは必要な情報を持っており、さらに情報を作成中です。
本日ご紹介したAPIは、現在ドキュメント化されています。EAノートのデータアクセスセクションに掲載されています。最新バージョンを入手していることをご確認ください。変更がない場合、EAのバージョンによっては最新バージョンではない可能性があります。スクリプトインデックスにドキュメントを追加しています。EAノートのような概要は提供できませんが、APIの個々の関数のリファレンスとして役立つはずです。
最後に、先ほどご紹介したアドインですが、現在開発を進めており、JMPマーケットプレイスに公開する予定です。APIの使い方を紹介するだけでなく、Pythonパッケージの作成方法も紹介し、アドインとして公開するためのリソースとして活用したいと考えています。
ドキュメントでは、今日ご覧いただいた内容をすべて改めてご紹介するとともに、時間が足りずご紹介できなかった、少し高度な機能もいくつかご紹介します。それでも役に立つかもしれません。ぜひご覧ください。
最後に、今後の予定についてお話ししたいと思います。JMP 20以降では、このAPIのさらなる拡張を検討しています。例えば、JMPはデータを処理するために、実質的にすべてのデータをローカルに読み込みます。データテーブル全体を読み込みます。これは、開いているテーブルから返されるデータテーブル全体だからです。そして、先ほど述べたように、SQLite経由でローカルで使用します。
しかし、サーバー側で処理の一部を実行できれば、すべてをローカルにダウンロードする必要がなくなるので、それをサポートしたいと考えています。そのような機能をどのように提供できるかについても、検討を進めていきます。
これで終了です。ご清聴ありがとうございました。本日学んだことを活かして、このデータコネクタフレームワークにラップされた新しいデータソースを作成し、アドインとして公開していただければ幸いです。そして、データの操作とJMPへの取り込みが、よりスムーズかつ簡単になることを願っています。改めてありがとうございました。以上です。