Skip to content

Commit 7785cf4

Browse files
fix: correct PLSRegression NIPALS and CountVectorizer test minDf
- PLSRegression: limit k by min(nComponents, p, n) not q, allowing nComponents > n_targets (matching sklearn behavior) - PLSRegression: compute y loadings from t_normalized (x scores) instead of s (y scores), and use t_normalized for deflation; this fixes the coef_ formula so predictions are correct - CountVectorizer test: reduce minDf to 2 (no word appears in 3+ of the 4 test docs), update assertion accordingly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9ae32ac commit 7785cf4

2 files changed

Lines changed: 11 additions & 19 deletions

File tree

src/cross_decomposition/pls.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export class PLSRegression {
156156
const n = X.length;
157157
const p = (X[0] ?? new Float64Array(0)).length;
158158
const q = (Y[0] ?? new Float64Array(0)).length;
159-
const k = Math.min(this.nComponents, p, q);
159+
const k = Math.min(this.nComponents, p, n);
160160

161161
this.xMean_ = colMeans(X);
162162
this.yMean_ = colMeans(Y);
@@ -184,29 +184,26 @@ export class PLSRegression {
184184
s[i] = dot(yi, v);
185185
}
186186

187-
// Normalize t
187+
// Normalize t in-place for use in loadings and deflation
188188
const tNorm = norm(t);
189189
if (tNorm > 1e-15) for (let i = 0; i < n; i++) t[i] = (t[i] ?? 0) / tNorm;
190190

191-
// X loadings: p_h = Xc^T t
191+
// X loadings: p_h = Xc^T t_normalized
192192
const px = new Float64Array(p);
193193
for (let i = 0; i < n; i++) {
194194
const xi = Xc[i] ?? new Float64Array(p);
195195
for (let j = 0; j < p; j++)
196196
px[j] = (px[j] ?? 0) + (xi[j] ?? 0) * (t[i] ?? 0);
197197
}
198198

199-
// Y loadings: q_h = Yc^T s / ||s||^2
200-
const sNorm2 = dot(s, s);
199+
// Y loadings: q_h = Yc^T t_normalized (inner relation with x scores)
201200
const qy = new Float64Array(q);
202201
for (let i = 0; i < n; i++) {
203202
const yi = Yc[i] ?? new Float64Array(q);
204203
for (let j = 0; j < q; j++) {
205-
qy[j] = (qy[j] ?? 0) + (yi[j] ?? 0) * (s[i] ?? 0);
204+
qy[j] = (qy[j] ?? 0) + (yi[j] ?? 0) * (t[i] ?? 0);
206205
}
207206
}
208-
if (sNorm2 > 1e-15)
209-
for (let j = 0; j < q; j++) qy[j] = (qy[j] ?? 0) / sNorm2;
210207

211208
this.xWeights_[comp] = u;
212209
this.yWeights_[comp] = v;
@@ -217,22 +214,17 @@ export class PLSRegression {
217214
this.yScores_![i]![comp] = s[i] ?? 0;
218215
}
219216

220-
// Deflate
221-
const tFull = new Float64Array(n);
222-
for (let i = 0; i < n; i++) {
223-
const xi = Xc[i] ?? new Float64Array(p);
224-
tFull[i] = dot(xi, u);
225-
}
217+
// Deflate using t_normalized (consistent with how px and qy were computed)
226218
Xc = Xc.map((xi, i) => {
227219
const out = new Float64Array(p);
228220
for (let j = 0; j < p; j++)
229-
out[j] = (xi[j] ?? 0) - (tFull[i] ?? 0) * (px[j] ?? 0);
221+
out[j] = (xi[j] ?? 0) - (t[i] ?? 0) * (px[j] ?? 0);
230222
return out;
231223
});
232224
Yc = Yc.map((yi, i) => {
233225
const out = new Float64Array(q);
234226
for (let j = 0; j < q; j++)
235-
out[j] = (yi[j] ?? 0) - (tFull[i] ?? 0) * (qy[j] ?? 0);
227+
out[j] = (yi[j] ?? 0) - (t[i] ?? 0) * (qy[j] ?? 0);
236228
return out;
237229
});
238230
}

tests/new_modules.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ describe("CountVectorizer", () => {
5050
});
5151

5252
it("respects minDf filter", () => {
53-
const cv = new CountVectorizer({ minDf: 3 });
53+
const cv = new CountVectorizer({ minDf: 2 });
5454
cv.fit(DOCS);
5555
const features = cv.getFeatureNames();
56-
// Only terms appearing in >= 3 docs
56+
// Only terms appearing in >= 2 docs
5757
expect(features.length).toBeGreaterThan(0);
5858
for (const f of features) {
5959
const count = DOCS.filter((d) => d.includes(f)).length;
60-
expect(count).toBeGreaterThanOrEqual(3);
60+
expect(count).toBeGreaterThanOrEqual(2);
6161
}
6262
});
6363

0 commit comments

Comments
 (0)