안녕하세요 여러분,
지난번 글을 올린 지 꽤 시간이 흘렀는데, 그동안 JMP 활용에 조금 더 능숙해진 것 같습니다. 부끄럽지 않게, 요즘 유행하는 AI-LLM을 활용하여 JSL을 스스로 학습하는 것이 꽤 효과적이었습니다. 그런데 JMP를 사용하면서 오랫동안 저를 괴롭혀 온 문제가 하나 생겼습니다.
제목에서 알 수 있듯이, 중복된 값 없이 큰 테이블을 연결한 결과입니다. 저는 JMP 18을 사용하고 있습니다. 테스트 데이터 테이블 두 개를 첨부했습니다(물론 매우 제한적이고 정리된 데이터 세트이지만, 실제 테이블에는 10만 개 이상의 행과 30개 이상의 열이 포함되어 있습니다). 이 테이블들은 프로젝트에 첨부해야 할 것 같습니다. 이런,
배경을 설명드리겠습니다(궁극적으로는 매우 직접적인 질문인데, 맥락이 많아서 죄송합니다).
정기적인 데이터 분석을 위한 상당히 발전된 워크플로가 있습니다. 샘플 ID와 관련 정보가 포함된 샘플 추적 테이블이 있습니다. 일반 데이터 테이블도 있는데, 이 테이블에 테스트 정보를 채웁니다. 두 테이블은 가상 조인을 사용하여 연결되며, 샘플 ID를 키로 사용합니다. 샘플 정보 테이블에서 각 샘플 ID는 고유합니다. 사이클 데이터 테이블에서 각 샘플 ID는 여러 행의 데이터를 가지며, 각 행에는 (이상적으로는) 고유한 사이클 ID가 있습니다. 샘플 ID와 사이클 ID의 각 고유한 조합에는 관련 데이터(다양한 성능 지표)가 있습니다.
테스트에는 여러 사이클이 있습니다.사실 테스트는 라이브 상태이고 사이클 번호와 관련 데이터는 지속적으로 업데이트됩니다.주기적으로 각 샘플의 사이클 데이터를 내보내고 JMP로 가져온 다음 두 테이블(최신 스냅샷과 대량 데이터 테이블)을 연결합니다.이 프로세스는 수많은 중복을 생성합니다.예를 들어 샘플 A를 살펴보겠습니다.대량 데이터 테이블의 A는 이미 사이클 ID 1 -> 1000을 가지고 있습니다.최신 스냅샷에서 A는 사이클 ID 1 -> 1200에 대한 데이터를 가지고 있습니다.테이블을 연결하면 대량 데이터 테이블에 샘플 A에 대한 사이클 ID 1 -> 1000 * 2, 1001 -> 1200 * 1이 있습니다.
제 개인적인 작업 흐름에는 이상적이지는 않지만 괜찮습니다. 모든 열을 선택하고, 중복된 행을 모두 선택한 후 삭제합니다. 최적은 아니지만, 뭐, 괜찮습니다.
문제는 사실 제가 최근 작업해 온 것에서 비롯됩니다. 제가 벌크 데이터를 구성하는 방식과 가상 조인을 결합하는 방식은 데이터 필터링에 매우 효과적인 전략입니다. 저희 팀원들도 이 점을 인지했고, 저는 여러 노력을 통해 다른 팀원들도 비슷한 전략을 채택하도록 설득했습니다. 하지만 저는 우리 팀에서 가장 숙련된 JMP 사용자입니다. 다른 팀원들은 JMP/JSL의 복잡한 부분을 다루는 법을 배우는 데 그만큼의 시간이나 의지를 갖고 있지 않습니다. 하지만 이는 괜찮습니다. 제가 기꺼이 맡는 책임입니다.
하지만 저는 그들이 제 데이터 처리 능력에 접근할 수 있기를 바랍니다.
해결책을 생각해 냈습니다. 대량 데이터 테이블을 외부 테이블로 유지하고, 관리자용 프로젝트를 사용하여 업데이트하는 것입니다. 프로젝트에서 자동 초기화되는 스크립트를 작성하여 마스터 파일의 로컬 임베디드 복사본을 해당 프로젝트에 자동으로 새로 고치는 함수를 정의했습니다. 이 스크립트는 잘 작동합니다. 다른 사용자 프로젝트에서 마스터 데이터 테이블의 스냅샷을 성공적으로 호출할 수 있습니다! 좋습니다. 매우 만족스럽습니다. 문제는 가장 논리적인 접근 방식은 해당 스냅샷에서 작업하지 못하게 하는 것입니다. 제가 작성한 스크립트는 새로 고침될 때마다 해당 테이블을 삭제하고, 그 자리에 동일한 이름의 새 테이블을 생성합니다. 이 방식을 최대한 유지하고 싶습니다. 사용자가 여러 스냅샷 시점에 다른 이름을 가진 마스터 테이블 사본 30개를 보유하는 것을 방지할 수 있기 때문입니다. 다음 단계는 마스터 복사본의 새 데이터로 사용자의 작업 데이터 테이블을 업데이트하는 것입니다.
그 작업을 위한 스크립트를 작성하려고 합니다. 사용자가 직접 추가한 내용은 그대로 유지하면서 작업 테이블을 업데이트해야 합니다.
Update() 함수를 사용해 보려고 했지만, 제가 아는 한 기존 데이터를 새 데이터로 바꾸는 것뿐입니다. 새 행을 추가하는 방법은 도저히 찾을 수가 없습니다. Join은 새 행을 추가하지만 새 테이블도 생성합니다. 두 방법 모두 효과가 없습니다. 제가 알아낸 유일한 방법은 연결하는 것입니다. 하지만 사용자가 중복 행을 처리하도록 하고 싶지 않습니다. 중복은 매우 심각할 수 있습니다. 데이터 테이블은 수십만 개의 행으로 이루어져 있고, 한 번에 최대 5만 개의 중복 행이 발생할 수 있습니다.
지금 제가 사용하는 방식(JSL 포함)은 마스터 테이블과 작업 테이블에 임시 열을 만드는 것입니다. 이 열은 샘플 ID || 사이클 ID를 연결하여 고유한 단일 열 키를 만들고 이를 keyKey라고 부르겠습니다. 그런 다음 마스터 테이블에 두 번째 열을 만들고 싶습니다. 이 열이 테이블 1과 테이블 2의 keyKey를 비교하도록 하고 싶습니다. 이를 Selector 열이라고 부르겠습니다. keyKey가 두 테이블에 모두 있는 경우 Selector 열의 값을 0으로 하고, 마스터에만 있는 경우 값을 1로 하고 싶습니다. 그런 다음 스크립트에서 값이 1인 모든 행을 선택하고 해당 선택을 기반으로 임시 하위 집합 테이블을 만든 다음 해당 하위 집합을 작업 테이블에 연결하도록 하고 싶습니다. 붐. 중복된 무료 연결입니다. 하지만 두 테이블 간의 keyKey를 비교하고 해당 비교를 사용하여 열 값을 생성하는 방법을 알아낼 수가 없습니다.
For Each Row() 루프 사용을 최대한 피하려고 합니다. 행 크기가 10만 개가 넘는 데이터 테이블에 메모리 부하가 발생하는 것을 원치 않습니다.
<
이름은 여기(1)에 기본으로 지정됩니다.
// --- 구성 ---
workingTableName = "작업_데이터_테스트";
readOnlyMasterName = "마스터_데이터_테스트";
keyColA = "셀 ID";
keyColB = "사이클 ID";
// --- 핸들 가져오기 및 오류 검사 수행 ---
dtWorking = 데이터 테이블(작업 테이블 이름);
dtMaster = 데이터 테이블(readOnlyMasterName);
dtWorking이 비어 있으면 dtMaster가 비어 있으면 두 테이블이 모두 열려 있는지 확인하세요.)를 throw합니다.
// --- 1단계: 기존 행 업데이트---
//Print( "1단계: 기존 행 업데이트..." );
//dtWorking << 업데이트(
// dtMaster와 함께,
// 열 일치( :Name(keyCols[1]) = :Name(keyCols[1]), :Name(keyCols[2]) = :Name(keyCols[2]) )
//);
// --- 2단계: 임시 연결 키를 사용하여 새 행 찾기 ---
Print( "2단계: For Each Row 루프를 명시적으로 사용하여 새 행 찾기..." );
// 먼저 두 테이블에 빈 임시 키 열을 추가합니다.
dtWorking << 새 열( "~temp_key~", 문자 );
dtMaster << 새 열( "~temp_key~", 문자 );
// 작업 테이블의 키를 채웁니다.
각 행에 대해(dtWorking,
:Name("~temp_key~") = :Name("셀 ID") || "|" || Char(:Name("사이클 ID"));
);
// 마스터 테이블의 키를 채웁니다.
각 행에 대해(dtMaster,
:Name("~temp_key~") = :Name("셀 ID") || "|" || Char(:Name("사이클 ID"));
);
// 작업 테이블에 있는 모든 임시 키의 빠른 조회 목록을 만듭니다.
workingKeys = 연관 배열( dtWorking:Name("~temp_key~") );
//마스터 테이블에 새로운 "플래그" 열을 추가합니다.
dtMaster << 새 열( "~is_match~", 숫자
// 수식을 사용하여 값을 설정합니다. 수식은 키가 새 키이면 0을 반환하고, 일치하면 1을 반환합니다.
Column( dtMaster, "~is_match~" ) << 수식 설정(
(workingKeys[ :Name("~temp_key~") ]가 누락된 경우)
0, // workingKeys에서 찾을 수 없음 -> 새 행입니다.
1 // workingKeys에서 발견됨 -> 기존 행임
)
);
// --- 3단계: 선택한 행에서 새 임시 테이블 만들기 ---
If( N Rows( dtMaster << 선택된 행 가져오기 ) > 0,
Print( "찾음 " || Char( N Rows( dtMaster << 선택한 행 가져오기 ) ) || " 새 행을 찾았습니다. 임시 테이블을 생성하는 중..." );
dtTempNewRows = dtMaster << Subset(
선택된 행( 1 ),
출력 테이블 이름( "Temp New Rows")
);
dtTempNewRows << 창 표시(1);
,
Print("추가할 새 행이 없습니다.");
);
// --- 4단계: 임시 열 정리 ---
//dtWorking << 열 삭제( "~temp_key~" )를 시도하세요.
//dtMaster << 열 삭제( "~temp_key~" )를 시도하세요.
//dtMaster << 선택 취소;
Print( "테스트가 완료되었습니다." );>
또한 누군가가 열 이름을 식별하기 위해 문자열 변수를 사용하는 방법을 알아내는 데 도움을 주면 좋겠습니다(1단계, 수정 방법을 알아낼 수 없어서 주석으로 처리했습니다).
어쨌든, 이 엄청난 글을 읽어주셔서 감사합니다.
늦은