엔지니어링 메일 백
에피소드 5 : I. 하다. 아니. 처럼. 거미 !!! (그리고 거미줄도 하이킹을 할 수 있습니다.)
나는 거미줄을 영원히 무시할 수 없다.
때때로 우리 시스템 엔지니어는 일반적인 범위를 다소 벗어난 흥미로운 질문에 직면합니다. JMP 용법. 이러한 응용 프로그램은 일반적으로 영리하며 종종 데이터 사용이 비즈니스 또는 기술 문제를위한 것이 아닌 방식을 집으로 가져옵니다. 다른 경우, 질문은 예상치 못한 도전적인 문제 일뿐입니다. 이러한 "곡선 공"(내가 부르고 싶은)은 코딩 문제, 흥미로운 분석, 데이터 시각화 방법 등 다양한 형태로 제공됩니다. 아이디어를 얻을 수 있습니다.
가끔은 제가 정말로 대답하고 싶지 않은 문제에 대한 질문을받을 것입니다. 오늘의 질문은 그 작은 더미에 있습니다. 연령 ! The Mail Bag의이 항목의 주제는 일반적으로 데이터 시각화 커뮤니티에서 무섭고 불쾌한 음모로 간주되므로 할로윈 게시물에 적합하다고 생각합니다.
이러한 유형의 플롯은 일상 업무에서 화장품 및 소비자 분석 고객에게 정기적으로 나타나기 때문에 영원히 무시할 수 없습니다. 그래서 저는 이것을 뉴욕과 뉴저지의 여러 사용자 그룹을 도울 수있는 기회로 삼고 있습니다. 그러나 나는 말해야한다… 나는 거미줄을 정말로 좋아하지 않는다.
질문
내가 말했듯이, 나는 내가 언급하고 싶은 것보다 더 자주이 질문을 받았다. 그래서 저는 실제 질문 이메일을 건너 뛸 것입니다. 당신은 몇 가지가 있었다는 것을 저를 믿어야합니다.
대면 요청도있었습니다.
그리고 JMP 커뮤니티에 대한 몇 가지 게시물.
네.
다음은 누군가가보고 싶은 내용에 대한 참고 자료로 제공 한 동료 검토 논문의 예입니다.
(제이. Inst. 꾸미다. 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)을 반환한다는 점에서 매우 멋진 코드입니다. BTW,이 함수는 JMP 샘플 응용 프로그램에 포함 된 나중에 참조하는 응용 프로그램에도 있습니다.
저는 Application Builder에서 작업하고 있기 때문에 제가하려고하는 거의 모든 작업은 함수 또는 표현식입니다. 이 프로젝트에 대한 모든 것 또한 놀라 울 정도로 반복적이므로 함수를 호출하는 경우 코드의 반복적 인 부분을 더 쉽게 읽을 수 있습니다.
쉬운 비트
결승전을 본다면 앱 소스 , JMP의 기존 부분 중 일부를 활용하고 있음을 알 수 있습니다. 병렬 플롯에는 그래프 빌더, 쌍별 상관 관계에는 다변량입니다. 거의 독점적으로 Application Builder의 JMP 생성 코드 또는 기능을 사용하여 생성되었습니다. 그래서 우리는 그들을 먼저 제거 할 것입니다.
이 경우 Graph Builder가 열을 처리하는 방식과 임의의 수의 변수를 처리 할 수 있어야한다는 사실 때문에 Graph Builder 스크립트를 표현식으로 빌드 한 다음이를 메인에 추가하는 것이 더 쉽습니다. 올바른 위치에 트리를 표시하십시오. 이에 대한 코드는 다음과 같습니다.
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를 사용하고 Append () . 나는 또한 사용하고 있습니다 평가 Insert () 구성.연결을 사용하여 문자열을 구성하는 것보다 읽기가 조금 더 쉽습니다.
다변량 플롯은 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;
);
보시다시피 많은 코드가 방사형 변환 등을 처리하고 있습니다.이 토론의 흥미로운 부분은 함수가 거의 끝날 때까지 발생하지 않습니다. (힌트 : 마커 그리기 주석.) 나는 마커 세그먼트 () Graph Elements 스택에 추가합니다. 완성 된 보고서에서 방사형 플롯을 마우스 오른쪽 버튼으로 클릭하고 커스터마이즈 , 거기에서 볼 수 있습니다.

그 이유 마커 세그먼트 () 너무 강력해서 행 상태 () 참조. JMP의 모든 것은 행 상태를 통해 데이터 테이블을 통해 연결됩니다. 따라서 보고서가 통신해야하는 데이터 테이블을 JMP에 알려 보고서의 모든 차트를 동기화하고 JMP와 익숙한 모든 상호 작용을 할 수 있습니다. 그리고 Markers에서 Marker Segments로 이동하는 것입니다.
스파이더 플롯
좋아, 우리는 댕 스파이더 플롯을 제외하고는 내가 말할 수있는 다른 것이 없다. 그래서 그것에 들어가 봅시다.
스파이더 플롯은 기본적으로 중심 축을 둘러싼 평행 플롯이기 때문에, 우리는 모든 점이 극 좌표계에 투영되는 평행 플롯을 만들어야 할 것입니다. 그리고 그게 고통스러워 보인다면 당신 말이 맞습니다. 그것은. 또한 정말 정말 뇌가 방사형 정보를 처리하기 어렵습니다. 따라서 Cartesian에서 극좌표로 변경하면 스파이더 차트를 읽기가 훨씬 더 어려워집니다. 어쨌든이 그래프의 장점에 대한 담론을 다음 시간 동안 저장하겠습니다. 코드에!
좋은 소식은 실제로 변수 당 하나의 차원 만 다루기 때문에 모난 좌표계의 일부입니다. 그래서 거기에 약간의 변형 작업이 있지만 그렇게 나쁘지는 않습니다. 사용자가 데이터가 실제로 포함하는 것보다 더 넓은 스케일을 표시하기를 원할 수 있다는 것을 고려해야 할 때, 예를 들어 값이 3에서 5 사이이지만 가능한 스케일은 1에서 7입니다. 축 열 속성을 찾은 다음 거기에서 최대 및 최소 값을 가져 와서 처리 할 수있었습니다. 이것은 내러티브에서 중요한 점이 아니므로 추가 기능에 포함 된 소스 코드에서 어떻게 수행되었는지 확인할 수 있습니다.
방사형 스포크와 기준선은 선() 명령. 이 모든 작업에서 가장 어려운 부분은 각 행의 웹을 대화 형으로 만들고 범례를 작동시키는 것이 었습니다. 나는 라인 Seg () 그리고 마커 세그먼트 () . 전설은 내가 최근에 만났던 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;
);
중요한 부분은 끝을 향한 라인 및 마커 세그먼트입니다. 각 연결선을 개별적으로 그려야했습니다. 이있다 경로 Seg () 그렇게하면 정말 쉽게 만들 수 있었 겠지만 색상을 지정하려고하면 채워진 다각형이 만들어집니다. 그래서 라인과 마커였습니다! 그래픽 세그먼트를 사용하면 행 상태 연산자를 통해 데이터 테이블에 쉽게 다시 연결할 수 있습니다. 이제 모든 그래프가 기본 데이터 테이블의 행 상태를보고 있기 때문에 데이터 테이블의 각 행 상태에 색상을 할당하여 모두 동시에 색상을 지정할 수 있습니다.
JMP에서 일부 그래프를 마우스 오른쪽 버튼으로 클릭하면 행 범례 옵션이 표시됩니다. 한동안 JMP에 있었고 두 가지를 수행합니다. 첫째, 데이터 테이블의 행을 열별로 채색합니다 (데이터 테이블의 빨간색 삼각형 메뉴 옵션처럼). 둘째, 시각화 옆에 작은 범례를 만듭니다. 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 개)이나 진드기 (거미류도 있지만 뱀파이어 거미입니다. 거미는 그들을 싫어합니다).