工程郵袋
第5集:I. 做。 不。 喜歡。 蜘蛛!!! (蜘蛛區也可以遠足。)
我不能永遠忽略蜘蛛圖,所以我們開始...
我們的系統工程師不時地遇到一些有趣的問題,這些問題可能會超出JMP用法。 這些應用程序通常都很聰明,經常使人們了解如何使用數據不僅用於業務或技術問題。 在其他時候,這些問題只是意外的,具有挑戰性的問題。 這些“曲線球”(我喜歡稱呼它們)有許多不同的形式:編碼問題,有趣的分析,可視化數據的方式……您就明白了。
有時,我會收到一些我真的不想回答的問題。 今天的查詢很少,我已經避免了年份! 數據可視化社區通常將“郵件袋”中該條目的主題視為恐怖,令人不快的情節,因此我認為它適合萬聖節帖子。
由於在我的日常工作中會定期為化妝品和消費者分析客戶顯示這種情節,所以我不能永遠忽略它。 因此,我以此為契機,為紐約和新澤西州的許多用戶群體提供了幫助。 但是我不得不說……我真的不喜歡蜘蛛劇情。
問題
就像我說的那樣,我收到這個問題的頻率比想提的要高。 因此,我將跳過實際的問題電子郵件,您只需要相信我有幾個問題即可。
也有一些親自要求。
以及JMP社區上的一些帖子。
是的
以下是一個經過同行評審的論文示例,該論文有人給我作為他們希望看到的參考:
(J. 研究所 釀造。 2012; 118:325–333)給任何可能正在閱讀此書的數據可視化專家的註解:請原諒。 我知道這是一個可怕的圖。
響應
從歷史上看,我對這些要求的一般回應都遵循大多數數據可視化專家所說的那樣,這是在所有可能的情況下,勸阻人們不要使用此圖。 但是,最近,我開始對這個持續存在的問題有所不同。 從那以後,我對這個問題採取了不同的策略。
我目前關於蜘蛛圖的理念是,如果我也能向人們展示更好的方式來展示相同的信息,我願意幫助人們了解如何製作一個。 就像情況一樣我回答的另一個可視化問題,對我而言,製作此圖表的過程比實際結果更具啟發性。
我恨。 蜘蛛。
好吧,多年來我對蜘蛛有強烈的反感。 我不會完全稱呼它為蜘蛛恐懼症,但多年來,我會極度偏見地派遣八隻腳(有時只有六隻腳;您永遠不能確定!)的東西。 我沒有憐憫。 我沒有給四分之一。 一世。 拿。 不,囚犯。 無論如何,我最終克服了它。 而且,除了of蟲(吸血鬼蜘蛛!)之外, 我與八足的朋友和睦相處。 現在,我們通常只是保持彼此的距離。 我想認為,我對蜘蛛圖的厭惡部分來自於我對蜘蛛的問題,但也可能是它們是一種皇家痛苦,我本能地知道製造一個蜘蛛會有些嚇人。 這並不是說在構建它們時不會涉及一些有趣的編碼問題,特別是如果您希望它們具有用戶希望從JMP獲得的交互性(只是我知道代碼本身會有點嚇人)。 無論如何,讓我們看一下問題。
解決問題
練習我的講道,我從一個問題陳述開始:
我想製作一個JMP加載項,它顯示蜘蛛圖和可視化框架中數據集的其他可能方式,以後可以添加到該框架中。
下一步是創建我需要做的不同事情的清單:
- 從用戶處獲取用於繪製圖形的尺寸。
- 從用戶獲取每個數據子集的標籤列。
- 為每個子集提供的尺寸構造一個交互式蜘蛛圖。
- 為每個子集提供的尺寸構造一個交互式徑向圖。
- 為每個子集提供的尺寸構造一個交互式平行圖。
- 為提供的維度構造成對相關的交互式表格。
範圍內的問題揭示了一些好消息,特別是,如果我使用Application Builder,則可以將很多這些東西卸載到JMP,這也將使以後在需要時添加新的可視化變得非常容易。 最具挑戰性的部分大多圍繞前兩個圖表,並使它們具有交互性。 JMP的圖形在笛卡爾坐標系中工作( X和ÿ坐標)。 出於所有意圖和目的,這些圖均處於極坐標系中( [R和φ坐標)。 結果,我將需要一些代碼在笛卡爾坐標系和極坐標系之間來迴轉換。 讓我們從這裡開始。
坐標變換功能
這是我用於兩個坐標系之間的函數:
polarToRect = Function( {r, t},
Eval List( {r Cosine( t ), r Sine( t )} )
);
這很簡單。 正是您從幾何教科書中找到的內容。 這是一段非常不錯的代碼,它需要半徑(r)和角度(t)並返回一個坐標對(x,y)。 順便說一句,此功能也在JMP示例應用程序中包含的我稍後引用的應用程序中。
因為我在Application Builder中工作,所以我要做的幾乎所有其他事情都是函數或表達式。 該項目的所有內容都具有重複性,因此,如果我只是調用函數,它將使代碼的迭代部分更易於閱讀。
易位
如果你看看決賽應用程式來源,您會看到我正在利用JMP的一些現有部分:使用Graph Builder進行並行繪製,使用Multivariate進行成對相關。 它們幾乎都是使用JMP生成的代碼或應用程序構建器中的功能創建的。 因此,我們將首先消除它們。
由於Graph Builder在這種情況下對待列的方式,以及我必須能夠處理任意數量的變量的事實,對於我來說,將Graph Builder腳本構建為表達式然後將其附加到main更為容易。在正確的位置顯示樹。 這是代碼:
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( \!"\!" )} )
)
))"
)
)
);
);
注意,我要做的只是將變量格式化為字符串,然後將其插入代碼中的正確位置,然後運行它。 最終結果是在我的應用程序中進行了完全交互式的並行繪圖,而我編寫的代碼很少。 我試圖手動編寫一個平行圖。它起作用了,但是幾乎沒有。 我不推薦。 只需使用Graph Builder,附加() 。 我也在用評估插入()構造。與使用串聯構造字符串相比,它更容易閱讀。
多元圖只是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習慣的所有交互性。 這只是從標記到標記細分(當我開始工作時,這讓我大吃一驚)。
蜘蛛劇情
好吧,除了那隻蜘蛛蜘蛛劇情,我們無話可說。 所以,讓我們開始吧。
由於蜘蛛圖基本上是圍繞中心軸纏繞的平行圖,因此我們或多或少必須製作平行圖-所有點都投影到極坐標系上。 而且,如果這聽起來很痛苦,那您是對的。 它是。 真的真大腦很難處理徑向信息。 因此,從笛卡爾坐標到極坐標的這一變化使得蜘蛛圖很難閱讀。 無論如何,我將再次將討論保留在此圖的優缺點上。 上代碼!
好消息是,由於我實際上只處理每個變量的一個維度,因此我可以完全控制角度的坐標系的一部分。 因此,那裡有一些轉換工作,但還不錯。 當您需要考慮用戶可能想要顯示比實際覆蓋的數據更寬的比例時,會變得混亂,例如,值在3到5之間,但可能的比例是1到7。 通過查找軸列屬性,然後從那裡獲取最大值和最小值,我能夠解決這個問題。 由於這不是敘述中的關鍵點,因此您可以在外接程序附帶的源代碼中看到如何做到這一點。
徑向輻條和參考線只是使用線()命令。 整個過程中最困難的部分是使每一行的網絡都具有交互性,並使圖例起作用。 我通過使用行Seg()和一個標記段() 。 這個傳說是對我遇到的一個非常古老的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;
);
重要的位是行和標記段的末尾。 我必須分別繪製每條連接線。 有一個路徑段()這樣做確實很容易,但是如果您嘗試為其著色,則會創建一個填充的多邊形。 因此,線條和標記是! 通過使用圖形段,通過“行狀態”運算符可以很容易地掛回到數據表。 現在,由於所有圖形都在查看主數據表中的行狀態,因此可以通過為數據表中的每個行狀態分配顏色來同時對它們進行著色。
右鍵單擊JMP中的某些圖形時,將出現“行圖例”選項。 它已經在JMP中使用了一段時間,並且做了兩件事。 首先,它按列為數據表中的行著色(就像數據表中的“紅色三角菜單”選項一樣)。 其次,它在可視化旁邊創建了一個小圖例。 我要做的就是讓顏色在四個圖表中相互同步。 這是代碼:
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;
);
由於我所有的圖形都在同一個數據表中查找行狀態,因此它們都將自動繼承行圖例分配的顏色! 超級光滑。
整理東西
就是這樣。 對於腳本編寫者,我希望您能從中受益的主要是Graphics Segments的功能。 這些功能可以做什麼,使JMP如此特別的許多方面都包含在內。 對於其他所有人,現在有蜘蛛圖加載項。 我為結果感到驕傲。 但是,請不要使用它。 有更好的可視化該類型數據的方法。;)
作者註
在本博客的寫作中,沒有蜘蛛受到傷害,儘管有些人可能不願與昆蟲(六足)或s蟲(也包括蜘蛛)混在一起,但它們是吸血鬼蜘蛛,所以我想認為蜘蛛討厭他們)。