エンジニアリングメールバッグ
第5話:わたし。 する。 ない。 のように。 スパイダー!!! (そして、スパイダープロットにもハイキングが必要です。)
I can't ignore spider plots forever, so here we go...I can't ignore spider plots forever, so here we go...I can't ignore spider plots forever, so here we go...I can't ignore spider plots forever, so here we go...スパイダーの陰謀をいつまでも無視するわけにはいかないので、ここで...
私たちシステム エンジニアは、一般的な範囲から多少外れるような興味深い質問に遭遇することがあります。 JMP使用法。 これらのアプリケーションは一般に賢く、多くの場合、データの使用がビジネス上または技術的な問題のためだけではないことを実感させられます。 また、質問が予想外の難しい問題であることもあります。 これらの「変化球」(私はそう呼んでいます)は、コーディングの問題、興味深い分析、データの視覚化方法など、さまざまな形で現れます...アイデアはおわかりでしょう。
時々、本当は答えたくない問題に関する質問を受けることがあります。 今日のクエリはその小さな山に含まれており、私はこれを避けてきました。年! The Mail Bag のこのエントリの主題は、一般にデータビジュアライゼーション コミュニティによって恐ろしく不愉快な陰謀とみなされているため、ハロウィーンの投稿としては適切だと思います。
この種のプロットは、化粧品や消費者分析の顧客に対して日常の仕事で定期的に現れるため、永遠に無視することはできません。 そこで、私はこれを機会として、ニューヨークとニュージャージーの多くのユーザー グループを支援することにしました。 しかし、私は言わなければなりません…私はスパイダープロットが本当に好きではありません。
質問
先ほども言いましたが、私はこの質問を、言いたくないほど頻繁に受けています。 したがって、実際の質問メールは省略しますが、いくつかの質問があったことを信じていただければと思います。
個人的なご要望もいくつかありました。
JMP コミュニティへの投稿もいくつかあります。
うん。
以下は、誰かが見たいものについての参考として私にくれた、査読済みの論文の例です。
(J. Inst. Brew. 2012; 118: 325–333) Author's note to any data visualization specialist that may be reading this: Please forgive me. I know this is an awful graph.(J. Inst. Brew. 2012; 118: 325–333) Author's note to any data visualization specialist that may be reading this: Please forgive me. I know this is an awful graph.(J. Inst. Brew. 2012; 118: 325–333) Author's note to any data visualization specialist that may be reading this: Please forgive me. I know this is an awful graph.(J. Inst. Brew. 2012; 118: 325–333) Author's note to any data visualization specialist that may be reading this: Please forgive me. I know this is an awful graph.(J. 研究所 醸造。 2012年; 118: 325–333) これを読んでいる可能性のあるデータ視覚化の専門家への著者のメモ: ご容赦ください。 これがひどいグラフであることは承知しています。
応答
これらのリクエストに対する私の一般的な対応は、これまでほとんどのデータ視覚化専門家が言っていたことと同じであり、可能であれば人々がこのプロットを使用することを思いとどまるようにするものです。 しかし、最近、私はこのしつこい質問について、少し違う考え方をするようになりました。 それ以来、私はこの問題に対して別の見方をするようになりました。
スパイダー プロットに関する私の現在の哲学は、同じ情報を途中で提示するより良い方法も示すことができれば、スパイダー プロットの作り方を人々が理解できるよう手助けすることに前向きです。 の場合と同様に、私が答えた他の視覚化に関する質問, 私にとって、このグラフを作成するプロセスは、実際の結果よりもはるかに有益でした。
私は嫌いです。 スパイダー。
そうですね、私は長年にわたってクモに対して強い反感を持ってきました(というか、持っていました)。 本格的なクモ恐怖症とは言えませんが、長年にわたり、私は極度の偏見を持って、足が 8 本あるもの (場合によっては 6 本の場合もあります。絶対に確信を持てません!) を送ってきました。 容赦はしなかった。 私は四分も与えなかった。 私。 取った。 いいえ、囚人です。 とにかく、最終的には乗り越えました。 そして、ダニ(吸血蜘蛛!)を除いて、 8本足の友達と和解しました。 今は基本的にお互いの邪魔をしないようにしています。 私の蜘蛛の陰謀に対する嫌悪感の一部は、蜘蛛の問題から来ていると思いたいのですが、もしかしたら蜘蛛は王室の苦痛であり、蜘蛛を作るのは少し怖いだろうと本能的に分かっていたのかもしれません。 だからといって、特にユーザーが JMP に期待する対話性を持たせたい場合には、その構築に興味深いコーディング上の問題が含まれるわけではありません (ただ、コード自体が少し恐ろしいものになることはわかっていました)。 とにかく、問題を見てみましょう。
問題の範囲を定める
私が説いていることを実践する、私は問題文から始めました。
スパイダー プロットや、後で追加できるフレームワークでデータ セットを視覚化するその他の可能な方法を表示する JMP アドインを作成したいと考えています。
次のステップは、やらなければならないさまざまなことのパンチ リストを作成することです。
- ユーザーからグラフ化するための寸法を取得します。
- データ サブセットごとにユーザーからラベル列を取得します。
- 各サブセットごとに、指定されたディメンションのインタラクティブなスパイダー チャートを作成します。
- 各サブセットごとに、指定されたディメンションのインタラクティブな放射状チャートを作成します。
- 各サブセットごとに、指定されたディメンションのインタラクティブな並列グラフを作成します。
- 指定されたディメンションのペアごとの相関関係の対話型テーブルを作成します。
問題の範囲を絞り込むと、いくつかの良いニュースが明らかになりました。具体的には、Application Builder を使用すると、多くの作業を JMP にオフロードできるため、必要に応じて後で新しい視覚エフェクトを追加するのも非常に簡単になります。 困難な部分は主に最初の 2 つのグラフと、それらをインタラクティブにする周りにありました。 JMP のグラフはデカルト座標系 (バツそしてy座標)。 これらのグラフは、どう見ても極座標系 ( rおよびφ座標)。 その結果、デカルト座標系と極座標系の間で相互に変換するためのコードが必要になります。 そこから始めましょう。
座標変換関数
2 つの座標系の間で作業するために使用した関数は次のとおりです。
polarToRect = Function( {r, t},
Eval List( {r Cosine( t ), r Sine( t )} )
);
とてもシンプルです。 まさに幾何学の教科書に載っているような内容です。 これは、半径 (r) と角度 (t) を受け取り、座標ペア (x,y) を返すという点で、非常に優れたコードです。 ところで、この関数は、後で参照する JMP サンプル アプリケーションに含まれるアプリケーションにも含まれています。
私はアプリケーション ビルダーで作業しているため、これから行うことはほぼすべて関数または式になります。 このプロジェクトのすべてが見事に反復的であるため、関数を呼び出すだけの場合、コードの反復部分が読みやすくなります。
簡単なコツ
決勝戦を見てみるとアプリのソースを見ると、JMP の既存の部分 (並列プロットにはグラフ ビルダー、ペア相関には多変量) を利用していることがわかります。 これらは、ほぼ例外なく、JMP で生成されたコードまたはアプリケーション ビルダーの機能を使用して作成されました。 したがって、最初にそれらを邪魔にならないようにします。
この場合、グラフ ビルダーが列を処理する方法と、任意の数の変数を処理できなければならないという事実により、グラフ ビルダー スクリプトを式として構築してメインスクリプトに追加する方が簡単です。ツリーを正しい場所に表示します。 そのコードは次のとおりです。
drawParallel = Function( {pCol},
{default Local},
// build the list of variables with the format "X( :col1), X( :col2)" as a string
// also building a list of plot elements
colStr = "";
gElemStr = "";
For( i = 1, i <= N Items( pCol ), i++,
If( i == 1,
colStr = colStr || "X(" || Char( pCol[i] ) || ")";
gElemStr = gElemStr || "X(" || Char( i ) || ")";
,
colStr = colStr || ", X(" || Char( pCol[i] ) || ", Position( 1))";
gElemStr = gElemStr || ", X(" || Char( i ) || ")";
)
);
// Get the grouping variable as a string
xVarStr = Char( gCol );
// Insert the strings into the main string
Eval(
Parse(
Eval Insert(
"parallelVLB << Append(Graph Builder(
Size( 766, 256 ),
Show Control Panel( 0 ),
Show Legend( 0 ),
Variables( ^colStr^, Color( ^xVarStr^ ) ),
Elements( Parallel( ^gElemStr^, Legend( 3 ) ) ),
SendToReport(
Dispatch(
{},
\!"Graph Builder\!",
OutlineBox,
{Set Title( \!"Parallel Plot\!" ), Image Export Display( Normal )}
),
Dispatch( {}, \!"X title\!", TextEditBox, {Set Text( \!"Variables\!" )} ),
Dispatch( {}, \!"graph title\!", TextEditBox, {Set Text( \!"\!" )} )
)
))"
)
)
);
);
ここで行っているのは、変数を文字列としてフォーマットし、コード内の適切な場所に挿入して実行しているだけであることに注意してください。 最終的には、私の側ではほとんどコーディングを行わずに、アプリ内に完全にインタラクティブな並列プロットが作成されます。 並列プロットを手動でスクリプト作成しようとしました。それはうまくいきましたが、ギリギリでした。 お勧めしません。 グラフビルダーを使用するだけで、追加() 。 私も使っています評価挿入()構築する。連結を使用して文字列を構築するよりも少し読みやすくなります。
多変量プロットは、JMP の多変量プラットフォームの単純なパラメーター化にすぎません。 このビットを取得するためにコードを書く必要さえありませんでした。 あるよアドバンストマスタリングJMPウェビナーこれは、マウスをクリックするだけでそれを行う方法を示しています。 繰り返しになりますが、私は利便性のためにこれらの部分をこのようにしているだけであり、アプリのこの部分に膨大な時間を費やしたくなかったからです。それは怠けているのではなく、効率的なプログラマーであることです。 JMP からコードが提供されるので、時間を節約することもできます。
放射状プロット
放射状プロットは、実際には、JMP のサンプル ディレクトリに含まれるサンプル アプリのポート (いくつかの変更を加えたもの) です。 大きな変更点は、私が使用していることですマーカーセグメント()の代わりにマーカー() 。 そして、それは実は重要なポイントなのです。 作ることは可能ですマーカー()いくつかのマウス キャプチャ コマンドといくつかのロジックを使用して対話型ですが、マーカーセグメント()これらすべてを処理します自動的に。 どのデータテーブルを監視する必要があるかを指示するだけです。
drawFlies = Function( {nP, pCol, obj},
{default Local},
// Create a list of angles for the spider plot
th = ((1 :: nP) - 1);
th = Shape( th, nP, 1 );
th = 2 * Pi() * th / nP;
//Get the data from the table as a matrix.
sel_dat = J( N Row( DataTable1 ), N Items( pCol ), 0 );
For( i = 1, i <= N Items( pCol ), i++,
sel_dat[0, i] = pCol[i] << get as matrix
);
// now get a matrix with two columns and nvars rows to keep track of the min and max of each variable
min_max = J( nP, 2, 0 ); // column1: minimum, column2: maximum. row for each variable chosen
For( i = 1, i <= nP, i++,
min_max[i, 1] = Min( sel_dat[0, i] );
min_max[i, 2] = Max( sel_dat[0, i] );
);
// Create some empty matrices for the converted values
xMat = [];
yMat = [];
// Convert the data matrix
For( i = 1, i <= nR, i++,
current = sel_dat[i, 0];
// for each variable, subtract the minimum and then divide by the range.
adj = J( N Row( current ), 1, 0.0 ) + 1.0 * ((current - min_max[0, 1]`) (min_max[0, 2] - min_max[0, 1])`);
adj = adj`;
total = Sum( adj );
{x, y} = polarToRect( adj, th );
x = Sum( x ) / total;
y = Sum( y ) / total;
xMat = xMat |/ x;
yMat = yMat |/ y;
);
// Draw markers
annotateRad = Expr(
// Matrices and book-keeping variables
xCoord = xxx;
yCoord = yyy;
obj[FrameBox( 1 )] << Append Seg( Marker Seg( xxx, yyy, Row States( DataTable1 ) ) );
);
// Substitute values into the annotation expression
Substitute Into( annotateRad, Expr( xxx ), xMat, Expr( yyy ), yMat );
annotateRad;
);
ご覧のとおり、コードの大部分は放射状変換などを処理しています。この議論の興味深い部分は、関数の終わりに近づくまで起こりません。 (ヒント:マーカーを描くコメント)を追加しています。マーカーセグメント()グラフ要素スタックに追加します。 完成したレポートの放射状プロットを右クリックし、カスタマイズ、そこに表示されます。

その理由は、マーカーセグメント()とても強力なのは、それがあるということです行の状態()その中での参考。 JMP のすべては行の状態を介してデータ テーブルにリンクされていることに注意してください。 したがって、レポートがどのデータ テーブルと通信する必要があるかを JMP に指示することで、レポート内のすべてのグラフを同期し、JMP で使い慣れたすべての対話機能を利用できるようになります。 それは、マーカーからマーカー セグメントに移動するだけです (これが機能するようになったとき、私は驚きました)。
蜘蛛の陰謀
OK、あの恐ろしい蜘蛛の陰謀以外に話すことは何もない。 それでは、本題に入りましょう。
スパイダー プロットは基本的に中心軸の周囲に巻き付けられた平行プロットであるため、多かれ少なかれ、すべての点を極座標系に投影した平行プロットを作成する必要があります。 それが苦痛に聞こえるかもしれませんが、それは正しいです。 そうです。 それも本当に、本当に脳が放射状の情報を処理するのは困難です。 したがって、その 1 つの変更 (デカルト座標から極座標へ) によって、スパイダー チャートが大幅に読みにくくなります。 とにかく、このグラフの利点に関する議論は別の機会に取っておきます。 コードに移りましょう!
良いニュースは、実際に変数ごとに 1 つの次元だけを扱っているため、変数を完全に制御できることです。角張った座標系の一部。 したがって、そこにはいくつかの変換作業がありますが、それほど悪いことではありません。 ユーザーがデータが実際にカバーするよりも広いスケールを表示したい可能性があることを考慮する必要がある場合、厄介になります。たとえば、値は 3 ~ 5 ですが、可能なスケールは 1 ~ 7 です。 軸の列プロパティを検索し、そこから最大値と最小値を取得することで、これを処理できました。 これは物語の重要なポイントではないため、アドインに含めたソース コードでそれがどのように行われたかを確認できます。
ラジアル スポークと基準線は、ライン()指示。 この全体の中で最も困難な部分は、各行のウェブをインタラクティブにし、凡例を機能させることでした。 を使用してラインをインタラクティブにしました。ラインセグメント()そしてマーカーセグメント() 。 このレジェンドは、私が最近遭遇した Row Legend と呼ばれる非常に古い JMP の一部を再利用したものでした。 それぞれを見てみましょう。
スパイダー チャートに線を作成するコードは次のとおりです。 放射状チャートと同様に、グラフに意味のある形式にするだけでも多くのデータ操作が必要です。
drawTrails = Function( {nP, pCol, obj},
{default Local},
// Create a list of angles for the spider plot
th = ((1 :: nP) - 1);
th = Shape( th, nP, 1 );
th = 2 * Pi() * th / nP;
//Get the data from the table as a matrix.
sel_dat = J( N Row( DataTable1 ), N Items( pCol ), 0 );
For( i = 1, i <= N Items( pCol ), i++,
sel_dat[0, i] = pCol[i] << get as matrix
);
// now get a matrix with two columns and nvars rows to keep track of the min and max of each variable
min_max = J( nP, 2, 0 ); // column1: minimum, column2: maximum. row for each variable chosen
// Check if the user wants to use axis column property values for max and min.
If( axisProp == 0,
// Directly calculate the values from the data table.
For( i = 1, i <= nP, i++,
min_max[i, 1] = Min( sel_dat[0, i] );
min_max[i, 2] = Max( sel_dat[0, i] );
)
,
// If the user wants to use axis column property values
For( i = 1, i <= nP, i++,
// Pull the column name and check if the axis property is defined
col = pCol[i];
axisFlag = Contains( col << Get Properties List, Expr( Axis ) );
//If the axis property is present pull the values from the property
If( axisFlag == 1,
axisPresent = col << Get Property( "Axis" );
min_max[i, 1] = Eval( Extract Expr( axisPresent, Min( Wild() ) ) );
min_max[i, 2] = Eval( Extract Expr( axisPresent, Max( Wild() ) ) );
,
// if the property is not present, calculate the values directly from the data
min_max[i, 1] = Min( sel_dat[0, i] );
min_max[i, 2] = Max( sel_dat[0, i] );
);
)
);
// Create some empty matices for the converted values
xMat = [];
yMat = [];
// Convert the data matrix
For( i = 1, i <= nR, i++,
current = sel_dat[i, 0];
// for each variable, subtract the minimum and then divide by the range.
adj = J( N Row( current ), 1, 0.0 ) + 1.0 * ((current - min_max[0, 1]`) (min_max[0, 2] - min_max[0, 1])`);
adj = adj`;
// an error check form missing values in the vector
For( k = 1, k <= N Rows( adj ), k++,
If( Is Missing( adj[k] ),
adj[k] = 0
)
);
{x, y} = polarToRect( adj, th );
xMat = xMat |/ x;
yMat = yMat |/ y;
);
// Draw Lines and Markers for spider plot
annotateSpider = Expr(
// coordinate matrices
xMat = xxx;
yMat = yyy;
nParam = nnn;
nPoints = N Rows( xMat );
nGroups = nPoints / nParam;
// Reshape the matrices to make them easier to work with
xSMat = Shape( xMat, nGroups );
ySMat = Shape( yMat, nGroups );
// Create an empty string to hold a list (as a string)
pathList = "";
// Draw the lines as polygons using the reshaped matrix
For( i = 1, i <= nGroups, i++,
// Get the coordinates for a given path and add the first value to the end to close the path
xCoord = xSMat[i, 0];
xCoord = xCoord || xCoord[1];
yCoord = ySMat[i, 0];
yCoord = yCoord || yCoord[1];
//
obj[FrameBox( 1 )] << Append Seg( Line Seg( xCoord, yCoord, Row States( DataTable1, {i} ) ) );
);
// Draw the markers
For( i = 1, i <= nP, i++,
// Get the coordinates for a given path
xCoord = xSMat[0, i];
yCoord = ySMat[0, i];
// Run Markers Seg (need to move and reformat the matrix)
obj[FrameBox( 1 )] << Append Seg( Marker Seg( xCoord, yCoord, Row States( DataTable1 ) ) );
);
);
// Substitute values into the annotation expression
Substitute Into( annotateSpider, Expr( xxx ), xMat, Expr( yyy ), yMat, Expr( nnn ), nP );
// Run the annotation expression
annotateSpider;
);
重要な部分は、最後の部分にある Line セグメントと Marker セグメントです。 接続線を個別に描く必要がありました。 がありますパスセグメント()これは非常に簡単ですが、色を付けようとすると塗りつぶされた多角形が作成されます。 というわけで、「ラインとマーカー」でした! グラフィック セグメントを使用すると、Row States 演算子を介してデータ テーブルに簡単に接続できます。 ここで、すべてのグラフはメイン データ テーブルの行状態を参照しているため、データ テーブル内の各行状態に色を割り当てることで、すべてのグラフに同時に色を付けることが可能になります。
JMP の一部のグラフを右クリックすると、[行凡例] オプションが表示されます。 これは JMP にしばらく存在しており、2 つのことを行います。 まず、データ テーブルの行を列ごとに色付けします (データ テーブルの赤い三角メニュー オプションと同様)。 次に、ビジュアライゼーションの横に小さな凡例が作成されます。 4 つのグラフ間で色を相互に同期させるために必要な作業はこれだけです。 コードは次のとおりです。
drawLegend = Function( {gVar, obj},
{default Local},
// extract the column name with the grouping variable
colName = gVar[1];
// pass the variables into the expression
annotateLegend = Expr(
obj[FrameBox( 1 )] << Row Legend( ggg, Color( 1 ), Continuous Scale( 0 ) )
);
// substitute into the expression
Substitute Into( annotateLegend, Expr( ggg ), colName );
// run the expression
annotateLegend;
);
すべてのグラフは行の状態について同じデータ テーブルを参照しているため、行の凡例が割り当てた色をすべて自動的に継承します。 とても滑らかです。
物事をまとめる
以上です。 スクリプターにとって、ここから得てほしい主な点は、グラフィック セグメントのパワーです。 JMP を特別なものにする多くの部分は、これらの関数が実行できる内容に集約されています。 他の人にとっては今があるスパイダープロットアドイン。 その結果をとても誇りに思います。 ただし、使用しないでください。 この種のデータを視覚化するには、もっと良い方法があります。 
著者のメモ
このブログを書く際にクモに危害は加えられませんでしたが、昆虫(6本足)やダニ(これもクモ類ですが吸血蜘蛛)と一緒にされることに憤りを感じた人もいるかもしれません。クモは彼らを嫌います)。