Effektive Verkettung ohne Duplikate mittels Skripting durch Abgleichen von Daten in zwei Spalten in unterschiedlichen TabellenHallo zusammen,
Mein letzter Beitrag ist schon eine Weile her, und ich glaube, ich bin in der Zwischenzeit etwas besser mit JMP geworden. Die Nutzung der boomenden KI-LLMs zum Selbstlernen von JSL hat sich schamlos bewährt. Allerdings stoße ich auf ein Problem, das mich bei JMP schon lange plagt.
Wie der Titel schon sagt, entsteht es durch die Verkettung großer Tabellen ohne Duplizierung von Werten. Ich verwende JMP 18. Ich habe zwei Testdatentabellen angehängt (mit offensichtlich sehr begrenztem und bereinigtem Datensatz; die echten Tabellen enthalten >100.000 Zeilen und >30 Spalten). Diese sollten wahrscheinlich einem Projekt angehängt werden, ups.
Lassen Sie mich den Hintergrund erklären (viel Kontext für eine letztlich sehr direkte Frage, entschuldigen Sie):
Ich verfüge über einen gut ausgearbeiteten Workflow für die regelmäßige Datenanalyse. Ich verfüge über eine Probenverfolgungstabelle mit Proben-IDs und den zugehörigen Informationen. Ich habe eine allgemeine Datentabelle, die ich mit Informationen aus meinen Tests fülle. Die beiden Tabellen sind über einen virtuellen Join verknüpft, wobei die Proben-ID als Schlüssel dient. In der Probeninformationstabelle ist jede Proben-ID eindeutig. In der Zyklusdatentabelle enthält jede Proben-ID mehrere Datenzeilen mit jeweils (idealerweise) einer eindeutigen Zyklus-ID. Jede eindeutige Kombination aus Proben-ID und Zyklus-ID verfügt über zugehörige Daten (verschiedene Leistungskennzahlen).
Ein Test umfasst mehrere Zyklen. Tatsächlich ist ein Test live, und die Zyklusnummer sowie die zugehörigen Daten werden ständig aktualisiert. Regelmäßig exportiere ich die Zyklusdaten für jede Probe, importiere sie in JMP und verbinde dann die beiden Tabellen – den neuesten Snapshot und die Massendatentabelle. Dieser Vorgang erzeugt zahlreiche Duplikate. Nehmen wir beispielsweise Probe A. A in der Massendatentabelle hat bereits die Zyklus-ID 1 -> 1000. Im neuesten Snapshot enthält A Daten für die Zyklus-ID 1 -> 1200. Ich verbinde meine Tabellen, und die Massendatentabelle enthält für Probe A die Zyklus-ID 1 -> 1000 *2 und 1001 -> 1200 *1.
Für meinen persönlichen Arbeitsablauf ist das nicht ideal, aber es ist in Ordnung. Ich wähle alle Spalten aus, wähle alle doppelten Zeilen aus und lösche sie. Nicht optimal, aber egal.
Das Problem liegt eigentlich an meiner jüngsten Arbeit. Die Art und Weise, wie ich meine Massendaten aufbaue und die Kombination mit virtuellen Verknüpfungen ist eine sehr effektive Strategie zur Datenfilterung. Mein Team hat das bemerkt, und mit einigem Aufwand konnte ich andere davon überzeugen, ähnliche Strategien zu übernehmen. Allerdings bin ich der fortgeschrittenste JMP-Anwender in unserer Gruppe. Die anderen haben nicht so viel Zeit und Engagement, sich mit den Feinheiten von JMP/JSL vertraut zu machen. Das ist in Ordnung, es ist eine Verantwortung, die ich gerne übernehme.
Aber ich möchte, dass sie Zugriff auf meine Fähigkeit zur Datenverarbeitung haben.
Ich habe eine Lösung gefunden. Die Idee ist, die Massendatentabelle als externe Tabelle zu verwalten und sie mithilfe eines Administratorprojekts zu aktualisieren. Ich habe Skripte geschrieben, die sich in ihren Projekten automatisch initialisieren und Funktionen definieren, um eine lokal eingebettete Kopie der Masterdatei automatisch in ihre Projekte zu aktualisieren. Diese funktionieren einwandfrei – ich kann erfolgreich auf einen Snapshot der Masterdatentabelle in den Projekten anderer Benutzer zugreifen! Super, damit bin ich sehr zufrieden. Das Problem ist, dass es am logischsten ist, ihnen die Arbeit an diesem Snapshot zu verbieten. Das Skript, das ich geschrieben habe, löscht die Tabelle bei jeder Aktualisierung und generiert stattdessen eine neue identische Tabelle mit demselben Namen. Das möchte ich unbedingt beibehalten – so verhindern wir, dass Benutzer zu verschiedenen Snapshot-Zeitpunkten 30 Kopien der Mastertabelle mit unterschiedlichen Namen haben. Im nächsten Schritt wird dann die eigene Arbeitsdatentabelle des Benutzers mit den neuen Daten aus der Masterkopie aktualisiert.
Ich versuche, ein Skript zu schreiben, um dies zu erreichen. Ich brauche es, um die Arbeitstabelle zu aktualisieren und gleichzeitig alles beizubehalten, was die Benutzer selbst hinzugefügt haben.
Ich habe versucht, die Update-Funktion () zu verwenden, aber soweit ich weiß, ersetzt sie nur vorhandene Daten durch neue. Ich kann einfach nicht herausfinden, wie ich damit neue Zeilen hinzufügen kann. Join fügt zwar neue Zeilen hinzu, erstellt aber auch eine neue Tabelle. Keine der Lösungen funktioniert bei mir. Die einzige Möglichkeit, die ich kenne, ist die Verkettung. Ich möchte aber nicht, dass die Benutzer mit doppelten Zeilen zu kämpfen haben. Die Duplikate können auch sehr schwerwiegend sein – die Datentabellen bestehen aus Hunderttausenden von Zeilen, und es können bis zu 50.000 Duplikate gleichzeitig vorhanden sein.
Mein aktueller Ansatz (einschließlich JSL) besteht darin, temporäre Spalten in der Master- und der Arbeitstabelle zu erstellen. Diese verknüpfen die Beispiel-ID || Zyklus-ID und erstellen einen eindeutigen einzelnen Spaltenschlüssel, nennen wir ihn „keyKey“. Dann möchte ich eine zweite Spalte in der Mastertabelle erstellen. Diese Spalte soll den „keyKey“ in Tabelle 1 und Tabelle 2 vergleichen, nennen wir sie die „Selector“-Spalte. Wenn der „keyKey“ in beiden Tabellen vorhanden ist, soll der Wert in der „Selector“-Spalte 0 sein. Wenn er nur in der Mastertabelle vorhanden ist, soll der Wert 1 sein. Dann möchte ich, dass das Skript alle Zeilen mit dem Wert 1 auswählt, eine temporäre Teiltabelle basierend auf dieser Auswahl erstellt und diese Teilmenge dann mit der Arbeitstabelle verknüpft. Boom. Duplikatsfreie Verkettung. ABER. Ich kann beim besten Willen nicht herausfinden, wie ich den „keyKey“ zwischen zwei Tabellen vergleiche und diesen Vergleich zum Generieren eines Spaltenwerts verwende.
Ich versuche verzweifelt, die Verwendung von For Each Row()-Schleifen zu vermeiden. Ich möchte diese Speicherbelastung bei Datentabellen mit mehr als 100.000 Zeilen vermeiden.
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." );
Außerdem wäre es großartig, wenn mir jemand dabei helfen könnte, herauszufinden, wie ich Zeichenfolgenvariablen zur Identifizierung von Spaltennamen verwenden kann (Schritt 1, auskommentiert, weil ich auch nicht herausfinden konnte, wie ich das beheben kann).
Wie dem auch sei, danke fürs Lesen dieses gigantischen Beitrags.
Spät
Automatisierung und SkriptingDatenzugriffErgebnisse teilen und kommunizierenBetreff: Effektive Verkettung ohne Duplikate mithilfe von Skripten durch Abgleichen von Daten in zwei Spalten in verschiedenen TabellenKönnten Sie „Verbinden“ -> „Neue Tabelle erstellen“ -> „Alte Tabelle mit der neu erstellten verknüpften Tabelle überschreiben“ verwenden, wenn Sie die ursprüngliche Tabelle beibehalten möchten?
Betreff: Effektive Verkettung ohne Duplikate mithilfe von Skripten durch Abgleichen von Daten in zwei Spalten in verschiedenen TabellenWenn Sie Jarmos Vorschlag nicht folgen können, können Sie immer noch Duplikate verketten und dann löschen.
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()
);
Betreff: Effektive Verkettung ohne Duplikate mithilfe von Skripten durch Abgleichen von Daten in zwei Spalten in verschiedenen Tabellen@jthi Ich möchte vermeiden, die Arbeitstabelle des Benutzers zu löschen. Daher ist die Verkettung, soweit ich es bisher verstehe, die einzig praktikable Option.
@mmarchandFSLR Ich denke, das scheint zunehmend die einfachste Lösung zu sein, auch wenn ich Bedenken habe, dass viele Duplikate die Dinge verlangsamen könnten. Ich bin einfach schockiert, dass es in JMP unmöglich zu sein scheint, nicht-duplizierte Daten zu verketten. Es fühlt sich an, als würde eine so gewaltige Funktion fehlen …
Betreff: Effektive Verkettung ohne Duplikate mithilfe von Skripten durch Abgleichen von Daten in zwei Spalten in verschiedenen TabellenAus diesem Grund habe ich vorgeschlagen, einen Join durchzuführen und dann die verknüpfte Tabelle zu verwenden, um die ursprüngliche Tabelle zu „aktualisieren“.
JMP verfügt derzeit nicht über eine richtige Upsert-Funktion (Aktualisieren + Einfügen), aber Sie können Ihre eigene schreiben. Es könnte allerdings etwas kompliziert sein, eine effiziente zu erstellen. Haben Sie es auch schon versucht, wenn Sie Probleme mit der Duplikatsauswahl haben? Oder wäre das nur eine voreilige Optimierung , um dies zu vermeiden?
Betreff: Effektive Verkettung ohne Duplikate mithilfe von Skripten durch Abgleichen von Daten in zwei Spalten in verschiedenen TabellenNein, du hast recht. Es war eine voreilige Optimierung, und ja, das Verketten und Löschen von Duplikaten funktioniert mit unserem Datensatz, obwohl dadurch mehrere hunderttausend Duplikate zum Löschen entstehen. Es scheint mir einfach ein hässlicher Weg zu sein, um eine meiner Meinung nach recht grundlegende Funktion zu erreichen.
Ich verstehe zwar die Idee hinter dem Join-Ansatz, aber wird er nicht trotzdem durch Update() blockiert, da keine neuen Zeilen hinzugefügt werden? Könnten Sie das bitte klarstellen, falls ich etwas falsch verstehe?
Für alle Neugierigen unten der endgültige bereinigte Code:
// ** 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;
Betreff: Effektive Verkettung ohne Duplikate mithilfe von Skripten durch Abgleichen von Daten in zwei Spalten in verschiedenen TabellenEs wird durch nichts blockiert. Meine Idee war, mit Join eine neue Tabelle mit allen Zeilen zu erstellen und diese dann zu verwenden, um alle Werte in der alten Tabelle zu überschreiben. Dies kann beispielsweise durch die Verwendung von Datentabellen-Subskription erreicht werden.
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);
Sie können möglicherweise auch so etwas tun, aber seien Sie vorsichtig, wie JMP möglicherweise Tabellenskripte/Variablen durcheinander bringen kann
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);