生のプローブ データからのデータ テーブルの生成 (抽出) に関する初歩的な質問皆さん、こんにちは。これがこのコミュニティへの最初の投稿です。最近転職したので、プローバーベンチテストとデータ分析をたくさん行う予定です。 JMPを少し使ってみましたが、全く進歩しませんでした。
現在、私のテストでは常に大きな CSV ファイルを生成し、測定データは不要な情報 (テストのセットアップ、条件など) と一緒に埋もれています。JMP スクリプトを使用して有用な情報を引き出すための支援を得て、ここから高度なスクリプトの学習を開始したいと思います。
添付のスクリーンショットで私のタスクを説明しています。右上は、巨大な CVS がどのようなものか (このような構造が何百個も含まれています) を示すための、単一の IV スイープ データ ポイントの例 (R を測定するため) です。「複数行の不要な情報」は、インポートしたくない不要な行を表します。I/V/R データ部分をコピーして貼り付けるだけでなく、いくつかの文字列からウェーハ ロット ID、ウェーハ ID、レチクル (またはステッパー フィールド) インデックス、およびサブサイト番号も抽出します。右上のボックスには、必要な JMP 出力が表示されます。
オプションとして、下の 2 つのボックスにはいくつかのトリビアがあります。左下にはデータ構造に関する一般的なコメントがあり、右下には特定の情報を抽出する必要がある文字列の意味が説明されています。
スクリーンショットで私の質問に十分な情報が得られることを願っています。以前は Excel VBA マクロを使用してそのような作業を行っていました。署名文字列をスキャンして検索し、その場所付近の特定の文字/文字列/行を選択的にインポートし、生データ ファイルの最後まで検索を続けました。 JMP スクリプトでも同じことができるのではないかと思います。
自動化とスクリプト作成Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問スクリーンショットはありません。小さな匿名化されたデータセットまたは .csv ファイルを添付することは可能でしょうか?
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問こんにちは、pmrozさん。迅速な返信ありがとうございます。最初に送信したときに添付ファイルを忘れましたが、1分以内に投稿を編集してスクリーンショットを添付しました。もう一度確認してください。
また、ご要望に応じて、Excel をここにアップロードします。ファイルをクリーンにするために、不要な情報のほとんどを削除しましたが、左上のボックスを「生データ」の例としてコピーして貼り付けることができます。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問表示されている形式は、読み込む際に大きな問題にはならないはずです。興味のない情報行は、データの読み込みを妨げる可能性があるため、心配です。もう 1 つの懸念は、プロジェクト名を含む行を JMP に認識させる方法です。プロジェクト名は常に Proj で始まるのでしょうか?
実際のファイルを提供すると非常に役立ちます。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問他の人が言っているように、これは実際のデータ形式に大きく依存します。以下は、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 で開くだけで解析します (実際のデータによっては、追加の手順が必要になる場合があります)。

Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問- サブサイトを見落としていました。申し訳ありません。スクリプトに追加しました。
- ディスカッション フォーラムの目的は、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 );
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問以前のアイデアを続行して、テキスト ファイルをセクションに分割し、それらのセクションを解析します (この録画では、このトピック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);

Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問以下は、サンプル 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]
)
)
);
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問こんにちは、 txnelson さん。よくおっしゃいました。怠惰で申し訳ありません。時間をかけて自分で答えを見つけられることはわかっています (長い目で見れば、私にとっては良いことです) が、できるだけ早く正しい答えを得なければならないというプレッシャーを感じました。
経験を積んだら、また戻ってきて、あなたがしてきたように新参者を助けたいと思います。ここは、みんなが互いに助け合う本当のユーザー コミュニティだと思います。それぞれの回答は、個々のニーズに合わせてカスタマイズされています。JMP は、ここでのアクティブなユーザーにインセンティブを与えるべきです。このスレッドでのあなたの回答が、他のエンジニア ユーザーにも役立つことを願っています。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問こんにちは txnelson さん、うまくいきました!来週のレポートにこのスクリプトが本当に必要になります。ありがとうございます!!!~~~
一方で、あまり複雑でなければ、改善していただくことは可能でしょうか?
1. サブサイト情報 (列) がありません。
// 将来の閲覧者の便宜を図るため、1 つのウェーハには複数のレチクル (ステッパー フィールドまたはダイとも呼ばれます) があり、各レチクルには固有の (インデックス X、インデックス Y) があり、各レチクルには「サブサイト」 (サブダイとも呼ばれます) と呼ばれる多数のデバイスがあります。測定はサブサイト レチクル ウェーハごとに行われます。

2. すべてのDPを1つのJMPテーブルに出力する
//あまり考えずに、あなたのスクリプトを完全な RAW ファイル (数百の DP...) に適用しました。何が起こったかはご想像のとおりです。永遠にポップアップする多数の JMP ウィンドウと格闘し、最終的にはタスク マネージャーに頼って降伏しました...
3. データ テーブル部分の列プロパティを文字から数値に変更します。
4. コーディングをより簡潔な形式に圧縮することは可能ですか?
// 現在のものは非常に直感的で、初心者の私には最適です。しかし、将来的には、より短いコードの方がユーザーフレンドリーになる可能性があります。複雑だったり、初めて見る人にとって使いにくくなったりした場合は、忘れてください。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問こんにちは jthi さん、ありがとうございます! 後でこの正規表現関数について学ぶことに時間を費やすつもりです。
このスレッドが必要な回答をくれるだけでなく (本当にありがたい)、同様のスキャンと抽出の JMP タスクの開始テンプレートとしても役立つことを願っています。将来の仕事のためにすべてのスクリプトを保存します。
このトピックにさらに貢献するために、このようなスクリプトを使用するときに最も起こりやすい問題は、ジョブ ID 形式の一貫性です。ジョブ ID の構築方法に細心の注意を払い、恣意的に変更しないでください。従わないとプログラミングが歪んでしまいます。2 つの例を挙げると、私のジョブ ID の標準形式は次のとおりです: Projname_Lot id_wafer id_personal note: index X index Y
しかし、これはベンチ テストであり、すべてのテストに手動で名前を付けたため、私自身がルールに違反し、上記のスタイルに厳密に従わなかったケースがいくつかありました。
1. ロット ID とウェハ ID の間の「_」を忘れました。たとえば、「753367.1_W11」ではなく、「753367.1W11」と入力したケースがあります。
2. 1 つではなく 2 つの個人メモを追加しました。たとえば、1 回目の実行後に再テストを行う場合、「_2nd_」ではなく「_1st_redo_」を使用します。情報セクションの区切りとして「_」を使用しているため、カウントが混乱し、そのセクションに続く部分で問題が発生します。
高度な JMP スクリプト作成の第一歩を踏み出す手助けをしてくれた jthi と txnelson に改めて感謝します。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問アプリケーションによっては、特定の行に一致する正規表現パターンに頼るのではなく、繰り返しパターンを探すことで、パーサーをより堅牢にできる場合があります。たとえば、プロジェクト名はMetaData行の間にあるようです。

想定できる内容に応じて、「行が Proj で始まる」または「行がこの正規表現パターンに一致する」ことに頼るのではなく、それらのメタデータと「空の」行に基づいて抽出できます。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問こんにちは、Jthiさん
このスクリプトを実行中に、なぜかこのエラーが発生します。ご指導いただけますか?

Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問興味深い指摘ですね、jthiさん。検討してみます。
情報はジョブID行に埋め込まれていることに注意してください。問題は、情報をどのように抽出するかであり、どのように見つけるかではありません。私の教訓は、「行がProjで始まる」という一貫性を保つことです。これは非常に重要です。これを再確認するために、パターン検証スクリプトを追加し、抽出された情報が期待通りであることを確認することができます。正規表現は、そのための直感的なツールです。
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問TEST_SECTION_END を定義しましたか?
Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問はい、先ほど投稿したものと同じスクリプトを使用しています。

Re: 生のプローブデータからのデータテーブル生成(抽出)に関する初心者の質問ああ…私のスクリプトでは何らかの理由で省略されているので、TEST_SECTION_END を特定する必要があります。ざっと見たところ、EMPTY_LINE と同じかもしれません。