異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を実行します。こんにちは、みんな、
前回の投稿から少し時間が経ちましたが、その間にJMPの使い方が少し上達したと思います。恥ずかしげもなく、今話題のAI-LLM(法学修士)コースを使って日本語(JSL)を独学で学ぶのは、なかなかうまくいっています。しかし、JMPに関して、長年悩まされてきた問題に遭遇しました。
タイトルの通り、重複した値のない大きなテーブルを連結した際に発生します。JMP 18を使用しています。テスト用のデータテーブルを2つ添付しました(明らかに非常に限定的で整理されたデータセットですが、実際のデータは10万行以上、30列以上です)。これらはプロジェクトに添付した方が良いかもしれません。おっと。
背景を説明させてください(結局のところ非常に直接的な質問のため、多くの背景があり、申し訳ありません)。
定期的なデータ分析のためのワークフローは、ある程度整備されています。サンプルIDと関連情報を含むサンプル追跡テーブルと、テストから得た情報を入力する一般データテーブルがあります。これら2つは、サンプルIDをキーとする仮想結合を使用してリンクされています。サンプル情報テーブルでは、各サンプルIDは一意です。サイクルデータテーブルでは、各サンプルIDに複数のデータ行があり、それぞれに(理想的には)一意のサイクルIDが割り当てられています。サンプルIDとサイクルIDの一意の組み合わせには、それぞれ関連データ(さまざまなパフォーマンス指標)があります。
テストには複数のサイクルが含まれます。実際、テストはライブであり、サイクル番号と関連データは常に更新されます。私は定期的に各サンプルのサイクルデータをエクスポートし、JMPにインポートして、最新のスナップショットとバルクデータテーブルの2つのテーブルを連結します。このプロセスにより、多数の重複データが作成されます。例えば、サンプル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 と呼びます。次に、マスター テーブルに 2 つ目の列を作成します。この列でテーブル 1 とテーブル 2 の keyKey を比較します。これをセレクター列と呼びます。keyKey が両方のテーブルに存在する場合はセレクター列の値を 0 にし、マスターにのみ存在する場合は値を 1 にします。次に、スクリプトで値が 1 のすべての行を選択し、その選択に基づいて一時的なサブセット テーブルを作成し、そのサブセットを作業テーブルに連結します。これで完了です。重複のない連結です。しかし、どうしても 2 つのテーブル間で keyKey を比較し、その比較を使用して列の値を生成する方法がわかりません。
For Each Row() ループの使用を極力避けようとしています。100,000行を超えるデータテーブルでは、メモリ負荷が大きくなるのは避けたいのです。
Names Default To Here(1);
// --- Configuration ---
workingTableName = "Working_Data_Test";
readOnlyMasterName = "Master_Data_Test";
keyColA = "Cell ID";
keyColB = "Cycle ID";
// --- Get Handles & Perform Error Checks ---
dtWorking = Data Table( workingTableName );
dtMaster = Data Table( readOnlyMasterName );
If( Is Empty( dtWorking ) | Is Empty( dtMaster ), Throw("Ensure both tables are open.") );
// --- STEP 1: Update existing rows---
//Print( "Step 1: Updating existing rows..." );
//dtWorking << Update(
// With( dtMaster ),
// Match Columns( :Name(keyCols[1]) = :Name(keyCols[1]), :Name(keyCols[2]) = :Name(keyCols[2]) )
//);
// --- STEP 2: Find new rows using a temporary concatenated key ---
Print( "Step 2: Finding new rows using explicit For Each Row loop..." );
// Add the blank temporary key columns to both tables first.
dtWorking << New Column( "~temp_key~", Character );
dtMaster << New Column( "~temp_key~", Character );
// Populate the key for the Working Table
For Each Row( dtWorking,
:Name("~temp_key~") = :Name("Cell ID") || "|" || Char(:Name("Cycle ID"));
);
// Populate the key for the Master Table
For Each Row( dtMaster,
:Name("~temp_key~") = :Name("Cell ID") || "|" || Char(:Name("Cycle ID"));
);
// Create a fast lookup list of all temporary keys in the working table.
workingKeys = Associative Array( dtWorking:Name("~temp_key~") );
//Add a new "flag" column to the master table.
dtMaster << New Column( "~is_match~", Numeric
//Set its value using a formula. The formula returns 0 if the key is new, 1 if it's a match.
Column( dtMaster, "~is_match~" ) << Set Formula(
If( Is Missing( workingKeys[ :Name("~temp_key~") ] ),
0, // Not found in workingKeys -> It's a new row
1 // Found in workingKeys -> It's an existing row
)
);
// --- STEP 3: Create a new temporary table from the selected rows ---
If( N Rows( dtMaster << Get Selected Rows ) > 0,
Print( "Found " || Char( N Rows( dtMaster << Get Selected Rows ) ) || " new rows. Creating temporary table..." );
dtTempNewRows = dtMaster << Subset(
Selected Rows( 1 ),
Output Table Name( "Temp New Rows" )
);
dtTempNewRows << Show Window(1);
,
Print("No new rows found to add.");
);
// --- STEP 4: Clean up temporary columns ---
//Try( dtWorking << Delete Columns( "~temp_key~" ) );
//Try( dtMaster << Delete Columns( "~temp_key~" ) );
//dtMaster << Clear Select;
Print( "Test complete." );
また、列名の識別に文字列変数を使用する方法 (手順 1、これを修正する方法もわからなかったためコメント アウトしました) を誰かが教えてくれると助かります。
とにかく、この巨大な投稿を読んでくれてありがとう。
遅い
自動化とスクリプトデータアクセス結果の共有と伝達Re: 異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を行う元のテーブルをそのままにしておきたい場合は、「結合 -> 新しいテーブルの作成 -> 新しく作成した結合テーブルで古いテーブルを上書き」を使用できますか?
Re: 異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を行うJarmo の提案に従えない場合は、連結してから重複を削除することもできます。
Names Default To Here( 1 );
dt = Data Table( "Master_Data_Test" );
dt2 = Data Table( "Working_Data_Test" );
dt << Concatenate( dt2, Append to first table );
dt << Select Duplicate Rows( Match() );
If( Length( dt << Get Selected Rows ) > 0, //In case no duplicate rows exist; don't want to delete everything!
dt << Delete Rows()
);
Re: 異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を行う@jthiユーザーの作業テーブルを削除したくないので、今のところ私の理解では、連結が唯一の実行可能な選択肢です。
@mmarchandFSLR重複が多すぎて処理が遅くなる可能性はありますが、それが最もシンプルな答えのように思えます。JMPで重複のないデータを連結できないのは驚きです。本当に大きな機能が欠けているように感じます…
Re: 異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を行うそのため、結合を実行し、結合されたテーブルを使用して元のテーブルを「更新」することを提案しました。
JMPには現在、適切なアップサート(更新+挿入)機能がありませんが、独自の機能を作成することは可能です。ただし、効率的な機能を開発するのは少し複雑になるかもしれません… また、重複選択で何か問題が発生した場合は試しましたか? それとも、重複を回避するための早すぎる最適化でしょうか?
Re: 異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を行ういいえ、その通りです。時期尚早な最適化でした。確かに、連結と重複の削除は、数十万もの重複データを削除する必要があるにもかかわらず、私たちのデータセットではうまく機能します。ただ、私から見ると非常に基本的な機能を実現する方法としては、あまりにも醜いように思えます。
結合アプローチの考え方は理解できましたが、Update() で新しい行が追加されないことでブロックされているのでしょうか?何か誤解していないでしょうか?詳しく説明していただけますか?
興味のある人のために、最終的に修正されたコードを以下に掲載します。
// ** Working table update script **
// Includes lockfile check to prevent reading a mid-update master table.
//V1.1 Includes ability to deal sensibly with changing timestamps while preserving deletion of duplicates
Names Default To Here(1);
// --- Configuration ---
workingTableName = "Test_WorkingTable"; //Change this to the EXACT name of the table you want to update
keyColA = "Sample ID";
keyColB = "Test ID";
// --- Path Definitions ---
// Build the path to the master file and lock file on the shared drive.
homeDir = Get Environment Variable( "USERPROFILE" );
If( Is Empty( homeDir ), homeDir = Get EnvironmentVariable( "HOME" ) );
sharePointRelativePath = "\OUR\INTERNAL\FILEPATH\";
masterFilePath = homeDir || sharePointRelativePath || "Data_File_Name.jmp";
lockFile = homeDir || sharePointRelativePath || "lockfile.txt";
// --- 1. Check for Lock File ---
// If the lockfile exists, stop all execution immediately.
If( File Exists( lockFile ),
Print( "UPDATE FAILED: The master data file is currently locked by an admin for updates." );
Print( "Please try again later." );
Stop(); // Halt the script
);
// --- 2. Get Handle to User's Working Table ---
dtWorking = Data Table( workingTableName );
If( Is Empty( dtWorking ),
Throw( "UPDATE ERROR: Your working table '" || workingTableName || "' could not be found. Please ensure it is open." )
);
// --- 3. Open a Fresh, Unlinked Copy of the Master Data ---
dtMaster = Open( masterFilePath, invisible );
If( Is Empty( dtMaster ), Throw("Could not open master data file from shared drive.") );
// ---3.5. Deal with the timestamp table variable. It will return an empty string if it doesn't exist, and if it does exist, delete the variable.---
// A. Read the timestamp from the master table into a script variable.
newTimestamp = dtMaster << Get Table Variable( "Last Updated" );
// If the variable was found, print it to the log.
If( !Is Empty( newTimestamp ),
Print( "--- Master data was last updated on: " || newTimestamp || " ---" );
,
Print( "--- This master data does not contain a 'Last Updated' timestamp. ---" );
);
// B. Delete the variable from the master table so it's a clean data source.
// We wrap in Try() in case an old master file doesn't have the variable.
Try( dtMaster << Delete Table Variable( "Last Updated" ) );
// --- 4. Concatenate the Clean Master to the Working Table ---
Print("Step 1: Appending master data to working data...");
dtWorking << Concatenate(
dtMaster,
Append to First Table
);
Close( dtMaster, NoSave ); // The fresh master copy is no longer needed.
// --- 5. Find and Delete Duplicate Rows ---
Print("Step 2: Identifying and removing duplicate rows...");
dtWorking << Select Duplicate Rows(
Match( As Column(keyColA), As Column(keyColB) )
);
nDuplicates = N Rows( dtWorking << Get Selected Rows );
If( nDuplicates > 0,
Print("Found and deleting " || Char(nDuplicates) || " duplicate rows.");
dtWorking << Delete Rows;
);
dtWorking << Clear Select;
// 6. If a new timestamp was found, set it on the working table.
If( !Is Empty( newTimestamp ),
dtWorking << Set Table Variable( "Last Updated", newTimestamp );
Print( "Working table timestamp updated to: " || newTimestamp );
);
Print( "Process complete. Final table has " || Char(N Rows(dtWorking)) || " rows." );
Window( workingTableName ) << Bring Window to Front;
Re: 異なるテーブルの2つの列のデータを一致させることにより、スクリプトを使用して重複のない効果的な連結を行う何もブロックされていません。私の考えは、Joinを使ってすべての行を含む新しいテーブルを作成し、それを使って古いテーブルのすべての値を上書きすることでした。これは、例えばデータテーブルのサブスクリプトを使用することで実現できます。
Names Default To Here(1);
dt_master = Open("$DOWNLOADS/Master_Data_Test.jmp");
dt_working = Open("$DOWNLOADS/Working_Data_Test.jmp");
dt_new = dt_master << Join(
With(dt_working),
Merge Same Name Columns,
Match Flag(0),
By Matching Columns(:Cycle ID = :Cycle ID, :Cell ID = :Cell ID),
Drop multiples(0, 0),
Include Nonmatches(1, 1),
Preserve main table order(1),
Output Table("updated master"),
private
);
dt_master << Delete Rows(1::NRows(dt_master));
dt_master << Add Rows(N Rows(dt_new));
dt_master[0,0] = dt_new[0,0];
Close(dt_new, no save);
おそらくこのようなことも可能ですが、JMPがテーブルスクリプトや変数を混乱させる可能性があるので注意してください。
Names Default To Here(1);
dt_master = Open("$DOWNLOADS/Master_Data_Test.jmp");
dt_working = Open("$DOWNLOADS/Working_Data_Test.jmp");
// JMP can mess with your Source script if you have one
has_update_script = Contains(dt_master << Get Table Script Names, "Update") > 0;
If(has_update_script,
dt << Rename Table Script("Update", "Original_Update");
);
// possibly unnecessary Update
dt_master << Update(
With(dt_working),
Match Columns(:Cycle ID = :Cycle ID, :Cell ID = :Cell ID)
);
dt_master << Delete Scripts("Source");
If(has_update_script,
Try(dt_master << Rename Table Script("Original_Update", "Source"));
);
dt_joined = dt_master << Join(
With(dt_working),
Merge Same Name Columns,
Match Flag(1),
Copy second table formula(0),
By Matching Columns(:Cycle ID = :Cycle ID, :Cell ID = :Cell ID),
Drop multiples(0, 0),
Include Nonmatches(0, 1),
Preserve main table order(1),
Output Table("joined"),
private
);
old_rows = Where(dt_joined, :Match Flag == 3); // 3 = "Both"
dt_joined << Delete Columns(1);
dt_joined << Delete Rows(old_rows);
dt_master << Concatenate(
dt_joined,
"Append to first table"
);
Close(dt_joined, no save);
wait(0);