关于从原始探测数据生成(提取)数据表的新手问题大家好,这是我在这个社区的第一篇帖子。我最近换了工作,会做很多探测器台架测试和数据分析。用过一点 JMP,但一点也不高级。
现在,我的测试总是生成一个很大的 CSV 文件,并且测量数据与不感兴趣的信息(例如测试设置、条件等)一起埋没,我希望获得一些帮助,使用 JMP 脚本提取有用的信息,并从这里开始我的高级脚本学习。
我在所附的屏幕截图中解释了我的任务。右上角是一个 IV 扫描数据点示例(用于测量 R),让您了解我的巨型 CVS 是什么样子(它包含数百个这样的结构)。“多行不感兴趣的信息”表示我不想导入的那些无用行。除了复制和粘贴 I/V/R 数据部分外,我还喜欢从一些字符串中提取晶圆批次 ID、晶圆 ID、标线(或称步进场)索引和子站点编号。右上角的框显示了我想要的 JMP 输出。
作为可选阅读,底部两个框中有一些琐事。左下角是我对数据结构的一般评论,右下角解释了我需要提取某些信息的字符串的含义。
希望屏幕截图能为我的问题提供足够的信息。我曾经使用 Excel VBA 宏进行这样的工作:扫描搜索签名字符串,然后有选择地导入该位置附近的特定字符/字符串/行,然后继续搜索直到原始数据文件的末尾。我想知道 JMP 脚本会做同样的事情吗?
自动化和脚本回复:关于从原始探测数据生成(提取)数据表的新手问题没有截图。是否可以附加一个小型匿名数据集或 .csv 文件?
回复:关于从原始探测数据生成(提取)数据表的新手问题您显示的格式不应该成为读取的大问题。我担心那些无趣的信息行,因为其中的内容可能会干扰数据的读取。我的另一个担忧是如何让 JMP 识别包含项目名称的行。它总是以 Proj 开头吗?
提供实际的文件将会非常有帮助。
回复:关于从原始探测数据生成(提取)数据表的新手问题就像其他人所说的那样,这可能高度依赖于您的实际数据格式。以下是使用从 excel 复制粘贴到文本文件的数据的示例
Names Default To Here(1);
filepath = "$DOWNLOADS/744027.txt";
txt = Load Text File(filepath);
lines = Words(txt, "\!N");
project_pattern = "^(.+)_(\d+\.\d*)(W\d{1,2})_(T\d+)_(.+):(.+)?";
subsite_pattern = "<SUBSITE>(\d+)</SUBSITE>";
datastart_pattern = "^DataName";
data_start = 0;
For Each({line, idx}, lines,
If(!IsMissing(Regex(line, project_pattern)),
matches = Regex Match(line, project_pattern);
projname = matches[2];
lotid = matches[3];
waferid = matches[4];
time = matches[5];
notes = matches[6];
reticles = Words(Trim Whitespace(matches[7]), " ");
, !IsMissing(Regex(line, subsite_pattern)),
subsite = Regex(line, subsite_pattern, "\1");
, !IsMissing(Regex(line, datastart_pattern)),
data_start = idx;
break();
);
);
data_str = Concat Items(lines[data_start::N Items(lines)], "\!N");
dt = Open(Char To Blob(data_str), "text");
dt << New Column("lot", Character, Nominal, Set Each Value(lotid));
dt << New Column("waferid", Character, Nominal, Set Each Value(waferid));
dt << New Column("x", Character, Nominal, Set Each Value(reticles[1]));
dt << New Column("y", Character, Nominal, Set Each Value(reticles[2]));
dt << New Column("subsite", Character, Nominal, Set Each Value(subsite));
dt << Move Selected Columns({:lot, :waferid, :x, :y, :subsite}, To First);
dt << Delete Columns("DataName");
它使用正则表达式查找特定行,然后解析它们。对于数据,它会找到其起始处的行索引,然后通过让 JMP 打开它来解析它(根据实际数据,这可能需要额外的步骤)。

回复:关于从原始探测数据生成(提取)数据表的新手问题嗨 pmroz,谢谢你的及时回复。我第一次提交时确实忘记了附件,但在 1 分钟内,我编辑了帖子并附上了截图。你可以再检查一下。
另外,根据您的要求,我在此处上传了 Excel。我删除了大部分无用的信息以使文件干净,但您可以复制并粘贴左上角的框作为“原始数据”示例。
回复:关于从原始探测数据生成(提取)数据表的新手问题- 我忽略了子站点,抱歉。我已将其添加到脚本中。
- 讨论论坛的目的是帮助 JMP 用户解决他们在运行 JMP 时遇到的问题。这不是免费的脚本编写服务。我为社区成员编写脚本的原因是帮助他们学习如何使用 JSL。我希望我提供的脚本的接收者花时间向他们学习,这样他们就能解决他们的所有问题。
- 我已将动态列(部分列属性)更改为数字
- 该脚本现在将每个创建的表附加在一起
- 关于压缩代码.....为什么?如果压缩的目的是让它运行得更快,我可以接受。但如果压缩一段代码让下一个需要查看它的人更难阅读,那么这段代码就不是好代码。因此,对于你的问题“代码是否可以压缩成更简洁的格式”,我的回答是,我相信可以。
names default to here(1);
clear symbols( dtOut );
file = pick file();
dt = Open(
File,
Import Settings(
End Of Line( CRLF, CR, LF ),
End Of Field( Comma, CSV( 0 ) ),
Treat Leading Zeros as Character( 1 ),
Strip Quotes( 1 ),
Use Apostrophe as Quotation Mark( 0 ),
Use Regional Settings( 0 ),
Scan Whole File( 1 ),
Treat empty columns as numeric( 0 ),
CompressNumericColumns( 0 ),
CompressCharacterColumns( 0 ),
CompressAllowListCheck( 0 ),
Labels( 0 ),
Column Names Start( 1 ),
First Named Column( 1 ),
Data Starts( 1 ),
Lines To Read( "All" ),
Year Rule( "20xx" )
)
);
// Change column names to simplify coding
for( i=1,i<=ncols(dt), i++, column(i)<< set name( "C" || char(i)));
// Create a base output table
dtFinal = New Table( "Final" );
// Loop through the data
for(theRow = 1, theRow <= NRows(dt), theRow++,
If( dt:C1[theRow] == "SetupTitle",
memoryProjectName = "";
memoryLotName = "";
memoryWaferID = "";
memoryT0 = "";
memory1strun = "";
memoryIndexX = .;
memoryIndexY = .;
memorySubsite = .;
setupFlag = 1;
projFlag = 0;
// If this is not the first read in table, then append the table
// to the Final table, and close the current dtOut table
If( nrows(dtFinal) != 0 | isEmpty( dtOut ) == 0,
dtFinal << concatenate( dtOut, append to first table(1));
close( dtOut, nosave )
)
,
dt:C1[theRow] == "" & setupFlag == 1,
projFlag = 1;
setupFlag = 0;
,
projFlag == 1 & dt:C1[theRow] != "",
dt:C1[theRow] = trim(dt:C1[theRow]);
memoryProjectName = word(1, dt:C1[theRow], "_");
memoryLotName = word(2, dt:C1[theRow], "_W");
memoryWaferID = word(3, dt:C1[theRow], "_W");
memoryT0 = word(4, dt:C1[theRow], "_W");
memory1strun = word(5, dt:C1[theRow], "_W:");
memoryIndexX = num(word(2, dt:C1[theRow], ": "));
memoryIndexY = num(word(3, dt:C1[theRow], ": "));
memorySubsite = num(word(2, trim(dt:C1[theRow + 2 ]),"<>"));
projFlag = 0;
,
dt:C1[theRow] == "DataName",
colList = {};
col=2;
while( column(dt,col)[theRow] != "",
insert into(colList, column(dt,col)[theRow]);
col++
);
dtOut = New Table( "Lot " || memoryLotName || " WaferID " || memoryWaferID,
new column( "Project Name", character),
new column( "Lot Name", character),
new column( "WaferID", character),
new column( "Time Readout", character),
new column( "Personal Note", character),
new column( "SubSite", numeric, ordinal),
new column( "Index X", numeric, ordinal),
new column( "Index Y", numeric, ordinal)
);
for each( {colName}, colList,
dtOut << new column( eval(colName), numeric, continuous);
);
,
dt:C1[theRow] == "DataValue",
dtOut << Add Rows(1);
dtOut:Project Name[nrows(dtOut)] = memoryProjectName;
dtOut:Lot Name[nrows(dtOut)] = memoryLotName;
dtOut:WaferID[nrows(dtOut)] = memoryWaferID;
dtOut:Time Readout[nrows(dtOut)] = memoryT0;
dtOut:Personal Note[nrows(dtOut)] = memory1strun;
dtOut:SubSite[nrows(dtOut)] = memorysubsite;
dtOut:Index X[nrows(dtOut)] = memoryIndexX;
dtOut:Index Y[nrows(dtOut)] = memoryIndexY;
for each( {colName, index}, colList,
column( dtOut, colName )[nrows(dtOut)] = num(column(dt,index+1)[theRow])
)
)
);
// Close the last work file
close( dtOut, nosave );
// Uncomment the below line if you want the raw table deleted
// close( dt, nosave );
回复:关于从原始探测数据生成(提取)数据表的新手问题继续我之前的想法,将文本文件分成几部分,然后解析这些部分(此录音部分涵盖了这个主题Scripters Club 2024:第 2 节 - 准备非结构化传入数据进行分析)。
Names Default To Here(1);
// Test sections seem to always start with a line starting with "SetupTitle"
// and end to same UNLESS it is last section when it ends with
// line of ",,,,,,,,,,,,,,,,,,,,,,,"
TEST_SECTION_START = "SetupTitle, ";
EMPTY_LINE = ",,,,,,,,,,,,,,,,,,,,,,,";
// Section patterns for regex
PROJECT_PATTERN = "^(.+)_(\d+\.\d*)(W\d{1,2})_(T\d+)_(.+): (\d+ \d+)\,+?";
SUBSITE_PATTERN = "<SUBSITE>(\d+)</SUBSITE>";
DATASTART_PATTERN = "^DataName";
find_test_section_lines = function({lines}, {Default Local},
start_idx = 0;
end_idx = 0;
TEST_SECTION_START = TEST_SECTION_START; // JMP "feature", 00075244
TEST_SECTION_END = TEST_SECTION_END; // JMP "feature", 00075244
For Each({line, idx}, lines,
If(Starts With(line, TEST_SECTION_START),
If(start_idx == 0,
start_idx = idx;
,
end_idx = idx;
break();
);
);
);
If(start_idx != 0 & end_idx == 0,
end_idx = N Items(lines);
);
return(Eval List({start_idx, end_idx}));
);
parse_test_section = function({lines}, {Default Local},
data_start = 0;
PROJECT_PATTERN = PROJECT_PATTERN; //JMP "feature", 00075244
SUBSITE_PATTERN = SUBSITE_PATTERN; //JMP "feature", 00075244
DATASTART_PATTERN = DATASTART_PATTERN; //JMP "feature", 00075244
For Each({line, idx}, lines,
If(!IsMissing(Regex(line, PROJECT_PATTERN)),
matches = Regex Match(line, PROJECT_PATTERN);
projname = matches[2];
lotid = matches[3];
waferid = matches[4];
time = matches[5];
notes = matches[6];
reticles = Words(Trim Whitespace(matches[7]), " ");
, !IsMissing(Regex(line, SUBSITE_PATTERN)),
subsite = Regex(line, SUBSITE_PATTERN, "\1");
, !IsMissing(Regex(line, DATASTART_PATTERN)),
data_start = idx;
break();
);
);
data_str = Concat Items(lines[data_start::N Items(lines)], "\!N");
dt = Open(Char To Blob(data_str), "text", invisible);
For Each({colname}, Reverse(dt << Get Column Names("String")), // drop empty columns
If(Col Number(Column(dt, colname)) == 0,
dt << Delete Column(colname);
,
break(); // break on first "ok" column
);
);
// define order and names here
dt << New Column("project", Character, Nominal, Set Each Value(projname));
dt << New Column("lot", Character, Nominal, Set Each Value(lotid));
dt << New Column("waferid", Character, Nominal, Set Each Value(waferid));
dt << New Column("time", Character, Nominal, Set Each Value(time));
dt << New Column("notes", Character, Nominal, Set Each Value(notes));
dt << New Column("x", Character, Nominal, Set Each Value(reticles[1]));
dt << New Column("y", Character, Nominal, Set Each Value(reticles[2]));
dt << New Column("subsite", Character, Nominal, Set Each Value(subsite));
dt << Move Selected Columns({:project, :lot, :waferid, :time, :notes, :x, :y, :subsite}, To First);
dt << Delete Columns("DataName");
return(dt);
);
// Start parsing
filepath = "$DOWNLOADS/Raw data examples_3DP.csv";
txt = Load Text File(filepath);
lines = Words(txt, "\!N");
lines = Filter Each({line}, lines, line != EMPTY_LINE); // drop empty lines
{start, end} = find_test_section_lines(lines);
dt_result = Empty();
While(All(start, end),
cur_testset = Remove From(lines, start, end - 1);
dt = parse_test_section(cur_testset);
If(Is Empty(dt_result),
dt_result = dt;
,
dt_result << Concatenate(
dt,
"Append to first table"
);
Close(dt, no save);
);
{start, end} = find_test_section_lines(lines);
);
dt_result << Show Window(1);

回复:关于从原始探测数据生成(提取)数据表的新手问题这是一个脚本,它读取示例 CSV 文件中找到的所有 3 个项目数据并创建 3 个单独的数据表。
names default to here(1);
file = pick file();
dt = Open(
File,
Import Settings(
End Of Line( CRLF, CR, LF ),
End Of Field( Comma, CSV( 0 ) ),
Treat Leading Zeros as Character( 1 ),
Strip Quotes( 1 ),
Use Apostrophe as Quotation Mark( 0 ),
Use Regional Settings( 0 ),
Scan Whole File( 1 ),
Treat empty columns as numeric( 0 ),
CompressNumericColumns( 0 ),
CompressCharacterColumns( 0 ),
CompressAllowListCheck( 0 ),
Labels( 0 ),
Column Names Start( 1 ),
First Named Column( 1 ),
Data Starts( 1 ),
Lines To Read( "All" ),
Year Rule( "20xx" )
)
);
// Change column names to simplify coding
for( i=1,i<=ncols(dt), i++, column(i)<< set name( "C" || char(i)));
// Loop through the data
for(theRow = 1, theRow <= NRows(dt), theRow++,
If( dt:C1[theRow] == "SetupTitle",
memoryProjectName = "";
memoryLotName = "";
memoryWaferID = "";
memoryT0 = "";
memory1strun = "";
memoryIndexX = .;
memoryIndexY = .;
setupFlag = 1;
projFlag = 0;
,
dt:C1[theRow] == "" & setupFlag == 1,
projFlag = 1;
setupFlag = 0;
,
projFlag == 1 & dt:C1[theRow] != "",
dt:C1[theRow] = trim(dt:C1[theRow]);
memoryProjectName = word(1, dt:C1[theRow], "_");
memoryLotName = word(2, dt:C1[theRow], "_W");
memoryWaferID = word(3, dt:C1[theRow], "_W");
memoryT0 = word(4, dt:C1[theRow], "_W");
memory1strun = word(5, dt:C1[theRow], "_W:");
memoryIndexX = num(word(2, dt:C1[theRow], ": "));
memoryIndexY = num(word(3, dt:C1[theRow], ": "));
projFlag = 0;
,
dt:C1[theRow] == "DataName",
colList = {};
col=2;
while( column(dt,col)[theRow] != "",
insert into(colList, column(dt,col)[theRow]);
col++
);
dtOut = New Table( "Lot " || memoryLotName || " WaferID " || memoryWaferID,
new column( "Project Name", character),
new column( "Lot Name", character),
new column( "WaferID", character),
new column( "Time Readout", character),
new column( "Personal Note", character),
new column( "Index X", numeric, ordinal),
new column( "Index Y", numeric, ordinal)
);
for each( {colName}, colList,
dtOut << new column( eval(colName), numeric, continuous);
);
,
dt:C1[theRow] == "DataValue",
dtOut << Add Rows(1);
dtOut:Project Name[nrows(dtOut)] = memoryProjectName;
dtOut:Lot Name[nrows(dtOut)] = memoryLotName;
dtOut:WaferID[nrows(dtOut)] = memoryWaferID;
dtOut:Time Readout[nrows(dtOut)] = memoryT0;
dtOut:Personal Note[nrows(dtOut)] = memory1strun;
dtOut:Index X[nrows(dtOut)] = memoryIndexX;
dtOut:Index Y[nrows(dtOut)] = memoryIndexY;
for each( {colName, index}, colList,
column( dtOut, colName )[nrows(dtOut)] = column(dt,index+1)[theRow]
)
)
);
回复:关于从原始探测数据生成(提取)数据表的新手问题嗨, txnelson ,说得好。为我的懒惰道歉。我知道如果我花时间,我可以自己找到答案(从长远来看这对我有好处),但我感到有压力要尽快得到正确的答案。
当我积累了经验后,我会回来帮助新人,就像你一直在做的那样。我认为这是一个真正的用户社区,每个人都会互相帮助。每个答案都是根据个人需求量身定制的。JMP 真的应该激励这里的活跃用户。希望你在这个帖子中的回答也能让其他工程师用户受益。
回复:关于从原始探测数据生成(提取)数据表的新手问题你好 txnelson ,它有效!我真的需要这个脚本来编写下周的报告。谢谢!!!~~~
同时,如果不太复杂的话,能不能做一些改进?
1. 缺少子站点信息(列)。
//为了帮助未来的观众,一个晶圆有多个光罩(又名步进场或芯片);每个光罩都有自己独特的(索引 X、索引 Y);每个光罩都有许多称为“子站点”(又名子芯片)的设备。测量是按子站点-光罩-晶圆进行的。

2. 将所有 DP 输出到单个 JMP 表中
//没有想太多,我就将您的脚本应用到我的完整原始文件(数百个 DP...)中,您可以想象发生了什么:与无数永远弹出的 JMP 窗口作斗争,最终通过转向任务管理器投降...
3.将数据表部分列属性从字符更改为数字。
4. 是否可以将编码压缩成更简洁的格式?
//当前代码非常直观,对我这个初学者来说很棒。但是,将来,更短的代码可能会更加用户友好。如果它很复杂或不再适合首次查看者,请忘记它。
回复:关于从原始探测数据生成(提取)数据表的新手问题你好 jthi ,谢谢你!我以后一定会花时间学习这个正则表达式函数。
希望这个帖子不仅能给我所需的答案(非常感谢),还能作为类似扫描和提取 JMP 任务的起始模板。我会保存所有脚本以备将来的工作!
只是为了进一步促进这个主题,当我使用此类脚本时,最可能出现的问题是作业 ID 格式一致性。密切注意如何构造作业 ID,不要随意更改它,如果不遵循,这将扭曲编程。举两个例子,我的作业 ID 标准格式是:Projname_Lot id_wafer id_personal 注意:索引 X 索引 Y
但是,由于这是一次台架测试,并且我手动命名了所有测试,因此在某些情况下我自己违反了规则并且没有完全遵循上述风格:
1. 漏掉了 lot id 和 wafer id 之间的“_”,比如,有些情况下我输入的是“753367.1_W11”,而输入的是“753367.1W11”;
2. 添加了两个个人注释,而不是一个:例如,第一次运行后重新测试时,我使用“_1st_redo_”而不是“_2nd_”。由于我使用“_”作为信息部分分隔符,因此会导致那里出现问题,并且该部分后面的任何内容都会造成计数混乱。
再次感谢 jthi 和 txnelson 帮助我迈出高级 JMP 脚本的第一步!
回复:关于从原始探测数据生成(提取)数据表的新手问题根据您的应用程序,您可以通过查找重复模式而不是依赖匹配特定行的正则表达式模式来使解析器更加强大。例如,您的项目名称似乎位于 MetaData 行之间

根据您做出的假设,您可以基于这些元数据和“空”行来提取它,而不是依赖于“行以 Proj 开头”或“行与此正则表达式模式匹配”。
回复:关于从原始探测数据生成(提取)数据表的新手问题你好,Jthi,
我在运行这个脚本时不知怎么出现了这个错误。你能指导一下吗?

回复:关于从原始探测数据生成(提取)数据表的新手问题很有意思的观点,jthi。我会考虑的。
请注意,这些信息嵌入在作业 ID 行中,主要问题在于如何提取信息,而不是如何定位它。我的教训是确保“以 Proj 开头的行”保持一致。这非常非常重要。为了再次确认这一点,我们可以添加一个模式验证脚本,并确保提取的信息符合预期。正则表达式是一个直观的工具,可以实现此目的。
回复:关于从原始探测数据生成(提取)数据表的新手问题您是否定义了 TEST_SECTION_END?
回复:关于从原始探测数据生成(提取)数据表的新手问题是的,我使用的是您之前发布的相同脚本。

回复:关于从原始探测数据生成(提取)数据表的新手问题啊……不知什么原因,它们在我的脚本里被删掉了,你必须确定 TEST_SECTION_END 是什么。快速看一下,它可能和 EMPTY_LINE 一样。