엔지니어링 메일백
에피소드 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.(제이. 인스타그램 꾸미다. 2012; 118: 325–333) 이 글을 읽고 있는 데이터 시각화 전문가에게 보내는 메모: 용서해 주십시오. 나는 이것이 끔찍한 그래프라는 것을 알고 있습니다.
응답
이러한 요청에 대한 나의 일반적인 반응은 역사적으로 대부분의 데이터 시각화 전문가가 말한 것과 일치합니다. 즉, 가능하면 사람들이 이 플롯을 사용하지 않도록 하라는 것입니다. 하지만 최근에는 이 끈질긴 질문에 대해 조금 다르게 생각하기 시작했습니다. 그 이후로 나는 이 문제에 대해 다른 접근 방식을 취했습니다.
스파이더 플롯에 대한 나의 현재 철학은 동일한 정보를 제공하는 더 나은 방법을 보여줄 수 있다면 사람들이 스파이더 플롯을 만드는 방법을 알 수 있도록 열려 있다는 것입니다. 의 경우와 마찬가지로 내가 대답한 다른 시각화 질문 , 저에게는 실제 결과보다 이 차트를 만드는 과정이 훨씬 더 유익했습니다.
I. 증오. 거미.
좋아요, 저는 수년 동안 거미에 대해 강한 반감을 갖고 있었습니다. 나는 이것을 거미 공포증의 본격적인 사례라고 부르지는 않지만 수년 동안 다리가 8개 있는 것(때때로 6개; 확신할 수는 없음!)이 있는 것은 무엇이든 극도의 편견을 가지고 파견했습니다. 나는 자비를 베풀지 않았습니다. 나는 분기를주지 않았다. 나. 가지고 갔다. 아니요. 죄수들입니다. 어쨌든 결국 극복했어요. 그리고 진드기(뱀파이어 거미!)를 제외하고, 나는 다리가 여덟 개인 친구들과 화해했습니다. 우리는 일반적으로 지금은 서로의 길을 피하고 있습니다. 나는 거미 음모에 대한 나의 혐오감의 일부가 거미 문제에서 비롯된 것이라고 생각하고 싶습니다. 그러나 그것은 또한 그것이 왕실의 고통일 수도 있고 본능적으로 그것을 만드는 것이 약간 무서울 것이라는 것을 알았습니다. 그렇다고 이를 구성하는 데 흥미로운 코딩 문제가 없다는 것은 아닙니다. 특히 사용자가 JMP에서 기대하는 상호작용성을 갖기를 원하는 경우에는 더욱 그렇습니다(단지 코드 자체가 약간 위협적일 것이라는 것을 알고 있었기 때문입니다). 어쨌든 문제를 살펴보겠습니다.
문제 범위 지정
내가 설교한 것을 실천하기 , 나는 문제 설명으로 시작했습니다.
나중에 추가할 수 있는 프레임워크에서 데이터 세트를 시각화하는 스파이더 플롯과 기타 가능한 방법을 표시하는 JMP 추가 기능을 만들고 싶습니다.
다음 단계는 내가 해야 할 다양한 일의 펀치 리스트를 만드는 것입니다.
- 사용자로부터 그래프에 대한 치수를 가져옵니다.
- 각 데이터 하위 집합에 대해 사용자로부터 레이블 열을 가져옵니다.
- 각 하위 집합별로 제공된 차원에 대한 대화형 스파이더 차트를 구성합니다.
- 각 하위 집합별로 제공된 차원에 대한 대화형 방사형 차트를 구성합니다.
- 각 하위 집합별로 제공된 차원에 대한 대화형 병렬 차트를 구성합니다.
- 제공된 차원에 대한 쌍 상관 관계의 대화형 테이블을 구성합니다.
문제 범위를 조사하면 몇 가지 좋은 소식이 드러났습니다. 특히 Application Builder를 사용하면 이 많은 항목을 JMP로 오프로드할 수 있어 나중에 필요한 경우 새로운 시각화를 추가하기가 매우 쉬워집니다. 어려운 부분은 대부분 처음 두 차트 주변에 있었고 이를 대화식으로 만들었습니다. JMP의 그래프는 데카르트 좌표계( 엑스 그리고 와이 좌표). 이러한 그래프는 모든 의도와 목적을 위해 극좌표계( 아르 자형 및 ø 좌표). 결과적으로 데카르트 좌표계와 극좌표계 사이를 앞뒤로 변환하려면 몇 가지 코드가 필요합니다. 거기부터 시작합시다.
좌표 변환 함수
두 좌표계 간 작업에 사용한 함수는 다음과 같습니다.
polarToRect = Function( {r, t},
Eval List( {r Cosine( t ), r Sine( t )} )
);
꽤 간단합니다. 기하학 교과서에서 찾을 수 있는 것입니다. 반경(r)과 각도(t)를 사용하고 좌표 쌍(x,y)을 반환한다는 점에서 꽤 멋진 코드입니다. 그런데 이 기능은 JMP 샘플 애플리케이션에 포함되어 나중에 참조할 애플리케이션에도 있습니다.
저는 Application Builder에서 작업하고 있기 때문에 앞으로 수행할 다른 모든 작업은 함수나 표현식이 될 것입니다. 이 프로젝트의 모든 내용은 매우 반복적이므로 단지 함수를 호출하는 경우 코드의 반복 부분을 더 쉽게 읽을 수 있습니다.
쉬운 비트
결승전을 보면 앱 소스 를 보면 JMP의 일부 기존 부분(병렬 플롯을 위한 Graph Builder 및 쌍별 상관 관계를 위한 다변량)을 활용하고 있음을 알 수 있습니다. 이는 거의 독점적으로 Application Builder의 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에서 익숙한 모든 상호 작용 기능을 사용할 수 있습니다. Markers에서 Marker Segments로 이동하면 됩니다(작동할 때 정말 놀랐습니다).
거미 음모
좋아, 그 망할 거미 음모 외에는 내가 이야기할 수 있는 게 아무것도 없어. 그럼, 들어가 보겠습니다.
스파이더 플롯은 기본적으로 중심축을 중심으로 한 평행 플롯이므로 모든 점이 극좌표계에 투영된 평행 플롯을 만들어야 합니다. 그리고 그것이 고통스럽게 들린다면, 당신 말이 맞습니다. 그것은. 그것도 정말, 정말 뇌가 방사형 정보를 처리하기가 어렵습니다. 따라서 데카르트식에서 극좌표식으로의 변경으로 인해 스파이더 차트를 읽기가 훨씬 더 어려워졌습니다. 어쨌든, 이 그래프의 장점에 대한 논의는 다음 기회에 아껴두겠습니다. 코드를 살펴보세요!
좋은 소식은 실제로 변수당 하나의 차원만 다루기 때문에 변수를 완전히 제어할 수 있다는 것입니다. 모난 좌표계의 일부. 그래서 거기에 약간의 변형 작업이 있지만 그렇게 나쁘지는 않습니다. 사용자가 데이터가 실제로 포함하는 것보다 더 넓은 눈금 표시를 원할 수 있다는 점을 고려해야 할 경우에는 혼란스러워집니다. 예를 들어 값은 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;
);
중요한 비트는 끝 부분의 라인 및 마커 세그먼트입니다. 연결선을 하나하나 그려야 했어요. 이있다 경로 세그먼트() 그러면 정말 쉬웠을 텐데, 색칠하려고 하면 채워진 다각형이 만들어집니다. 그래서 라인과 마커가 그랬습니다! 그래픽 세그먼트를 사용하면 행 상태 연산자를 통해 데이터 테이블에 다시 연결하는 것이 정말 쉽습니다. 이제 모든 그래프는 기본 데이터 테이블의 행 상태를 보고 있으므로 데이터 테이블의 각 행 상태에 색상을 지정하여 동시에 색상을 지정하는 것이 가능해졌습니다.
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;
);
내 모든 그래프는 행 상태에 대해 동일한 데이터 테이블을 보고 있으므로 행 범례가 할당하는 색상을 모두 자동으로 상속합니다! 매우 매끄러운.
마무리
그리고 그게 다야. 스크립터의 경우 이 기능을 통해 가장 강조하고 싶은 점은 그래픽 세그먼트의 기능입니다. JMP를 특별하게 만드는 많은 부분은 이러한 기능이 수행할 수 있는 작업에 포함되어 있습니다. 다른 모든 사람들에게는 지금이 있습니다 스파이더 플롯 추가 기능 . 나는 그 결과가 매우 자랑스럽습니다. 하지만, 사용하지 마시기 바랍니다. 해당 유형의 데이터를 시각화하는 훨씬 더 나은 방법이 있습니다. 
저자의 메모
이 블로그를 쓰는 동안 거미는 해를 입지 않았지만 일부는 곤충(다리 6개)이나 진드기(거미류도 있지만 뱀파이어 거미이기 때문에)와 함께 있는 것에 분노했을 수도 있습니다. 거미는 그들을 싫어합니다).