大家好,
距离上次发帖已经有一段时间了,这段时间里,我觉得我的 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 函数会添加新行,但也会创建一个新表。这两种方法对我来说都行不通。我能想到的唯一方法是连接。但我不想让用户处理重复的行。重复的行也可能非常严重——数据表有数十万行,一次可能有多达 50,000 个重复项。
我现在的方法(包括 JSL)是在主表和工作表中创建临时列。这些列连接样本 ID || 周期 ID,创建一个唯一的单列键,我们称之为 keyKey。然后,我想在主表中创建第二列。我希望此列比较表 1 和表 2 中的 keyKey,我们称之为 Selector 列。如果 keyKey 存在于两个表中,我希望 Selector 列中的值为 0;如果它仅存在于主表中,我希望该值为 1。然后,我希望脚本选择值为 1 的所有行,基于该选择创建一个临时子集表,然后将该子集连接到工作表。Boom。无重复连接。但是。我终其一生都无法弄清楚如何在两个表之间比较 keyKey 并使用该比较来生成列值。
我拼命避免使用 For Each Row() 循环。我不希望超过 100,000 行的数据表占用太多内存。
<
名称默认为此处(1);
// - - 配置 - -
workingTableName = "工作数据测试";
readOnlyMasterName = “Master_Data_Test”;
keyColA = "小区ID";
keyColB = "循环ID";
// --- 获取句柄并执行错误检查 ---
dtWorking = 数据表(工作表名称);
dtMaster = 数据表( readOnlyMasterName );
如果(为空(dtWorking)|为空(dtMaster),则抛出(“确保两个表都已打开。”));
// --- 步骤 1:更新现有行---
//Print( "步骤 1:更新现有行..." );
//dtWorking<<更新(
// 使用(dtMaster),
// 匹配列(:名称(keyCols[1])=:名称(keyCols[1]),:名称(keyCols[2])=:名称(keyCols[2]))
//);
// --- 步骤 2:使用临时连接键查找新行 ---
Print(“步骤 2:使用显式 For Each Row 循环查找新行……”);
// 首先向两个表添加空白临时键列。
dtWorking << 新列( “~temp_key~”, 字符 );
dtMaster << 新列( “~temp_key~”, 字符 );
// 填充工作表的键
对于每一行(dtWorking,
:名称(“〜temp_key〜”)=:名称(“单元格ID”)||“|”|| Char(:名称(“周期ID”));
(此处似有缺失,请提供更正后的文本)。
// 填充主表的键
对于每一行(dtMaster,
:名称(“〜temp_key〜”)=:名称(“单元格ID”)||“|”|| Char(:名称(“周期ID”));
(此处似有缺失,请提供更正后的文本)。
// 创建工作表中所有临时键的快速查找列表。
workingKeys = 关联数组(dtWorking:名称("~temp_key~"));
//向主表添加一个新的“标志”列。
dtMaster << 新列(“~is_match~”,数字
//使用公式设置其值。如果键是新的,则公式返回 0;如果匹配,则返回 1。
列(dtMaster,“~is_match~”)<<设置公式(
如果(缺少(workingKeys[:Name("~temp_key~")]),
0, // 在 workingKeys 中未找到 -> 这是一个新行
1 // 在 workingKeys 中找到 -> 这是一个现有行
)
(此处似有缺失,请提供更正后的文本)。
// --- 步骤 3:从选定的行创建一个新的临时表 ---
如果(N 行(dtMaster << 获取选定行)> 0,
打印(“找到”|| Char(N Rows(dtMaster << 获取选定的行))||“新行。创建临时表...”);
dtTempNewRows = dtMaster << 子集(
选定行( 1 ),
输出表名称(“临时新行”)
(此处似有缺失,请提供更正后的文本)。
dtTempNewRows << 显示窗口(1);
,
Print("未找到要添加的新行。");
(此处似有缺失,请提供更正后的文本)。
// --- 步骤 4:清理临时列 ---
//尝试(dtWorking << 删除列( “~temp_key~” ) );
//尝试(dtMaster << 删除列( “~temp_key~” ) );
//dtMaster << 清除选择;
打印(“测试完成。”);>
此外,如果有人能帮助我弄清楚如何使用字符串变量来识别列名(步骤 1,已注释掉,因为我也不知道如何解决这个问题),那就太好了。
无论如何,感谢你阅读这篇巨著,
晚的