AlteryxのPython SDKが難しい?じゃあSnakeplaneを使おうぜ!

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、小澤です。

AlteryxではPython SDKを使うことでPythonを使ってツールが作成できます。 これを使うことで、マクロでの実装だけでは難しかった機能も実現できます。

しかし、元々のC++のコードをほぼラップしただけということもあり、 便利機能が用意されているわけではなく、ツール内部の処理やデータ構造などが複雑です。

そこで、今Alteryxが公式に提供しているフレームワークであるSnakeplaneの使い方を紹介します。

Snakeplaneとは

先ほど記載したようにSnakeplaneはPython SDKを使ってAlteryxのツールを作成する際に利用可能なフレームワークです。 Python SDKではほぼ生のAPIを意識した実装が必要になるため、 Alteryxの各種ツールがどのような動きをするかといった内部構造を理解している必要があったり、 本質的な処理以外で実装する必要がある部分が多かったりします。

SnakeplaneはAlteryxが公式で提供しているもので、Python SDKのツール実装が容易になる仕組みが提供されています。

特徴として

  • 必須なのは決められたデコレータを付けた3つの関数のみ
  • ListやDataFrameで値の受け渡しが可能
  • 1レコードずつ処理するか、全レコードまとめて処理するかを簡単に設定できる

というようなものが挙げられます。

実際使ってみると「おー、結構楽になってるな」と思うのですが、 これだけでは、よくわからないと思うので使ってみましょう。

というわけで使ってみる

今回は、以下のページにあるのと同じ、レーダーチャートを出力するツールを実装します。

ここで紹介されている内容は、IDE(PyCharm)を使って実装していたり、Pilot内で定義されているInvorkのコマンドを活用したり、デバッグの仕方を紹介してたりと、様々な便利な仕組みをあわせて紹介しています。 なんですが、まずは最低限必須のものを見ていくことにしましょう。

Pythonスクリプト以外で用意するもの

Python SDKを使ったツール開発で必要なものは以下の通りでした。

  • 処理内容を記述したPythonスクリプト
  • 依存ライブラリのrequirements.txt
  • Alteryx Designer上での設定画面のUIを定義したHTMLファイル
  • ツールに関する情報を記載したXMLファイル
  • アイコン画像のファイル
  • その他依存するもの

その他の部分は、Pythonスクリプトを複数ファイルに分割している場合やUIのHTMLでCSSやJavaScriptを外出ししてる場合となります。

Snakeplaneは、Pythonスクリプト部分のフレームワークとなるので、一番上のPythonスクリプト以外の要素に関しては通常のPython SDKを使ったツール開発と違いはありません。

HTML GUIの作成

HTML GUIの部分は従来のPython SDKを利用する場合と何ら変わりありません。 今回のレーダーチャートを出力するツールでは、以下の2つを用意します。

  • タイトルとなる値が含まれる列を選択するDropDown
  • レーダーチャートを構成する値の列を複数選択するListBox

以下のようにayxタグを2つ置いて、それぞれを定義します。

<article>
  <section>
    <div>XMSG("Title Field")</div><br>
      <ayx
        data-ui-props ="{type: 'DropDown'}"
        data-item-props ="{
          dataName: 'names',
          dataType: 'FieldSelector',
          anchorIndex: '0',
          connectionIndex: '0'}"> </ayx>
  </section><br><br>
  <section>
    <div>XMSG("Fields to Plot")</div><br>
      <ayx
        data-ui-props="{type: 'ListBox', placeholder: 'Select Fields', searchable: true}"
        data-item-props="{
          dataName: 'fields',
          dataType: 'FieldSelectorMulti',
          fieldType: 'Numeric',
          anchorIndex:'0',
          connectionIndex:'0'}"> </ayx>
  </section>
</article>

どちらも選択肢となる項目は入力のメタデータに基づいて列情報を取得する必要があるので、data-item-propsのdataTypeにてFieldSelectorを指定しています。

ayxタグ以外の部分も整えて以下のような設定画面となります。

なお、HTML GUI部分の完全な全体像は参照元の情報に記載がありますのでご確認ください。

Pythonスクリプトの作成

さて、ここからがいよいよ本番です。 Snakeplaneを使った実装を見ていきましょう。

まず最初にやるのは必要なライブラリのimportとSnakeplaneの初期化です。

import AlteryxPythonSDK as sdk

# Snakeplane使うよ!
from snakeplane.plugin_factory import PluginFactory

import matplotlib.pyplot as plt
from math import pi
import re

# PluginFactoryにツール名を渡す
# この時のツール名は設定のxmlファイルと一致させる必要がある
factory = PluginFactory("radarPlot")

続いて、処理内容の実装を行います。 実装するのは以下の3つの関数です。

  • 初期化を行う init 関数
  • ワークフロー実行時の処理を記述した process_data 関数
  • メタデータのやり取りに利用する build_metadata 関数

それぞれ順に見ていきましょう。 まずは init 関数です。

# 初期化用関数であることを示すデコレータ
@factory.initialize_plugin
def init(input_mgr, user_data, logger):
    # input_mgr.workflow_configからOrderedDict形式で設定値を取得可能
    # user_dataは他の2つの関数でも利用する値を入れておくために利用可能
    user_data.fields = input_mgr.workflow_config["fields"]
    user_data.names = input_mgr.workflow_config["names"]

    initSuccess = True

    # 設定値に不備があればエラーメッセージを出力して初期化失敗とする
    if user_data.fields is None:
        logger.display_error_msg("Select Fields to Plot")
        initSuccess = False

    if user_data.names is None:
        logger.display_error_msg("Select the Field that Contains the Plot Labels")
        initSuccess = False

    # 初期化がうまくいったかをbool型で返す
    return initSuccess

初期化に利用する関数であるということを示すために、 @factory.initialize_plugin デコレータを利用しています。 これが初期化用の関数であることを示しているため、引数同じ定義になっていれば関数名自体はinitである必要はありません。

init 関数では、設定値を受け取る処理を行います。 通常のPython SDKを使った処理では、AlteryxからXMLの設定情報がそのまま渡されていましたが、Snakeplaneを利用した際には引数で渡される input_mgr 内に workflow_config というOrderedDict型の変数がありますので、そこから取得可能です。 ただし、ここで取得する値はすべて文字列型となっているようなので必要に応じてキャストしてください。

設定値に不備がないかを確認して初期化が成功したかをbool型で返せばこの関数は終了です。

続いて確認するのはメインの部分となるワークフロー実行時の処理内容を記述する部分です。

# デコレータで実行時の処理であることを示す
# デコレータの引数に関しては後述
@factory.process_data(mode="stream", input_type="dataframe")
def process_data(input_mgr, output_mgr, user_data, logger):
    # 入力データを受け取る
    # 設定ファイルで記載した入力名を指定して取得可能
    input_anchor = input_mgr["Input"][0]
    data = input_anchor.data

    subset = data[user_data.fields.split(",")]
    name = data.iloc[0][user_data.names]

    categories = list(subset)
    N = len(categories)

    values = subset.loc[0].tolist()
    values += values[:1]
    values = [float(i) for i in values]

    angles = [n/float(N)*2*pi for n in range(N)]
    angles += angles[:1]

    ax = plt.subplot(111, polar=True)
    plt.xticks(angles[:-1], categories, color="grey", size=8)
    ax.set_rlabel_position(0)
    plt.yticks([50, 100, 150], ["50", "100", "150"], color="grey", size=7)
    plt.ylim(0, 200)
    ax.plot(angles, values, linewidth=1, linestyle='solid')
    ax.fill(angles, values, 'b', alpha=0.1)
    ax.set_title(name)

    # Alteryxが利用するtempフォルダにファイルを生成する
    filepath = output_mgr.get_temp_file_path()
    filepath = re.sub('tmp', 'png', filepath)
    filepath = filepath.replace('\\', '/')

    plt.savefig(filepath)
    plt.clf()

    # 出力側にデータを流す設定
    data['Figure'] = ['<img src="' + filepath + '" />"']
    output_anchor = output_mgr["Output"]
    output_anchor.data = data

factory.process_data では引数でいくつかの設定を行います。

modeは入力データの受け取り方となります。

"stream"を選択した場合、データを1レコード受け取りそれに対する処理を行います。 関数はデータ件数と同じ回数、呼び出されることになります。 Python SDKをそのまま使った際の ii_push_record と同じような動きになるわけです。

"batch"を指定した場合、すべてのデータをまとめて受け取ります。 ii_close で行っていた、集計処理などデータ全体が必要な場合はこちらを利用します。

"source"を指定した場合は入力を受け取らずに処理を行います。 自作コネクタを作成するなど、ツールが入力を受け取らない場合にはこの指定を行います。

input_typeでは入力して受け取る型を指定します。 この引数は"list"か"dataframe"のどちらかを指定可能です。

"list"を指定した場合、データをPythonのlist形式で受け取ります。 modeがbatchの時には行ごとのデータが入った2重のlistになります。

"dataframe"の場合は、PandasのDataFrame形式でデータを取得します。

また、今回は利用していませんが、chunk_size引数を使って一度にどの程度のまとまりまでデータを受け取るか指定可能です。

受け取ったデータは、通常のlistやDataFrameとしてそのまま利用可能なので、 Alteryxを意識することなく、通常のPythonプログラムと同様に記述可能です。

Alteryx側に出力を行う際には、引数のoutput_mgrを利用します。 input_mgrと同様、設定された出力コネクション名を使って出力先を指定します。

出力先の data 変数に対象となるデータを格納してやることでそのまま出力されます。

また、output_mgrにはその他にもいくつか便利な関数が用意されています。 今回は、 get_temp_file_path 関数を利用してAlteryx側で設定されている一時フォルダにレーダーチャートを画像ファイルとして出力しています。 その後、そのパスをAlteryxのレポートとして利用可能な形式で指定したものを出力のデータに含めているため、レーダーチャートが出力可能となっています。

Snakeplaneで指定が必要な最後の関数は build_metadata となります。

@factory.build_metadata
def build_metadata(input_mgr, output_mgr, user_data, logger):
    input_anchor = input_mgr["Input"][0]
    metadata = input_anchor.metadata
    metadata.add_column("Figure", sdk.FieldType.v_string, size=1000000, source="Report:Image:")

    output_anchor = output_mgr["Output"]
    output_anchor.metadata = metadata

この関数では、Alteryxが出力を行い際のデータそのものではなく、メタデータとなる情報を設定します。 Alteryxでは、列名や列の型などの情報をメタデータとして扱います。 この関数では、出力に際してそれらがどのような情報を持つかを定義するわけです。

今回は、入力となるデータに対して、レーダーチャートを追加したものを出力するので、 inputからコピーしたものに add_column で追加を行っています。

最後に、これらの3つの関数の定義の後、AyxPluginを作成します。

AyxPlugin = factory.generate_plugin()

その他の設定

設定用のXMLファイルは以下のようになります。 Python SDKのみを利用した場合と何ら変わりはなく、EngineDllEntryPointにこれまでで作成したスクリプトを指定しています。

<?xml version="1.0"?>
<AlteryxJavaScriptPlugin>
  <EngineSettings EngineDll="Python" EngineDllEntryPoint="main.py" SDKVersion="10.1" />
  <GuiSettings Html="gui/radarPlotGui.html" Icon="radarPlotIcon.png" SDKVersion="10.1">
    <InputConnections>
      <Connection Name="Input" AllowMultiple="False" Optional="False" Type="Connection" Label=""/>
    </InputConnections>
    <OutputConnections>
      <Connection Name="Output" AllowMultiple="False" Optional="False" Type="Connection" Label=""/>
    </OutputConnections>
  </GuiSettings>
  <Properties>
    <MetaInfo>
      <Name>Radar Plot</Name>
      <Description>Radar Plot Tool</Description>
      <ToolVersion>1.0</ToolVersion>
      <CategoryName>JS</CategoryName>
      <SearchTags>Pokemon</SearchTags>
      <Author>Alteryx</Author>
      <Company>Alteryx, Inc.</Company>
      <Copyright>2017</Copyright>
    </MetaInfo>
  </Properties>
</AlteryxJavaScriptPlugin>

また、依存ライブラリを定義したrequirements.txtはSnakeplaneの他、処理側で利用しているmatplotlib、input_typeでdataframeを指定しているのでpandasを入れてたものにしています。

pip install snakeplane pandas matplotlib pip freeze > requirements.txt

使ってみる

では、作成したツールを使ってみましょう。 Python SDKをそのまま利用している場合と同様、yxiファイルを作成してインストールすれば利用可能な状態となります。

参照元の記事にもあるようにKaggleのポケモンデータセットを利用します。 これには以下のようなデータが入っています。

列名 内容 備考
# 図鑑No. メガシンカも含むために全体で一意にはなっていない
Name 種族名
Type 1 1つ目のタイプ
Type 2 2つ目のタイプ ない場合はnull
Total 合計種族値
HP HP種族値 H
Attack 攻撃種族値 A
Defense 防御種族値 B
Sp. Atk 特攻種族値 C
Sp. Def 特防種族値 D
Speed すばやさ種族値 S
Generation 初登場の世代 タイトルではなく数値
Legendary 伝説か TrueかFalseのみで準伝説はTrue、幻はFalse

また、データは第6世代(XY)までのものとなっています。

CSV形式となっているので、まずはAuto Filedツールで型を設定します。 その後、今回は数件程度で動きを確認したいのでSampleツールで先頭3件(フシギダネ, フシギソウ, フシギバナ)までに絞っています。

その次の鰻丼のアイコンのツールが今回作成したものになります。 設定に関しては、UI部分の説明で画像と同じになります。

TitleにNameを、レーダーチャートで利用する値にHABCDSの6つを指定して実行した結果は以下のようになります。

おわりに

今回は、Alteyxが公式で提供するPython SDK用のラッパーであるSnakeplaneの紹介をしました。 Python SDKを生で利用した場合に比べて、コード記述量も減り、listやDataFrameでデータが扱えるのが素敵ですね。

Alteryxの導入なら、クラスメソッドにおまかせください

日本初のAlteryxビジネスパートナーであるクラスメソッドが、Alteryxの導入から活用方法までサポートします。14日間の無料トライアルも実施中ですので、お気軽にご相談ください。

alteryx_960x400