@@ -188,8 +188,8 @@ <h2>Raw metrics (median across datasets)</h2>
188188 </ div >
189189
190190 < div class ="card ">
191- < h2 > Performance vs test samples </ h2 >
192- < p class ="muted "> Each point is one crop×country dataset. X-axis: pooled walk-forward test rows (region-years).</ p >
191+ < h2 > Performance vs training set size </ h2 >
192+ < p class ="muted "> Each point is one crop×country dataset. X-axis: mean training rows across walk-forward splits (region-years used to fit ).</ p >
193193 < div class ="controls ">
194194 < label > Metric
195195 < select id ="scatter-metric-select "> </ select >
@@ -228,11 +228,19 @@ <h2>Performance vs test samples</h2>
228228 } ) ;
229229 if ( ! scatterMetricSelect . options . length ) {
230230 const opt = document . createElement ( "option" ) ;
231- opt . value = "r2 " ;
232- opt . textContent = "Overall R² " ;
231+ opt . value = "nrmse " ;
232+ opt . textContent = "Overall NRMSE " ;
233233 scatterMetricSelect . appendChild ( opt ) ;
234234 }
235235
236+ const scatterMetricByKey = Object . fromEntries (
237+ ( DATA . sample_scatter_metrics || [ ] ) . map ( m => [ m . key , m ] )
238+ ) ;
239+
240+ function scatterMetricLabel ( key ) {
241+ return ( scatterMetricByKey [ key ] || { } ) . label || key ;
242+ }
243+
236244 const horizonLabels = { eos : "End of season" , mid : "Mid-season" } ;
237245 Object . keys ( DATA . by_horizon ) . forEach ( hz => {
238246 const opt = document . createElement ( "option" ) ;
@@ -336,10 +344,12 @@ <h2>Performance vs test samples</h2>
336344 }
337345
338346 function renderScatterFamilies ( ) {
339- const metricKey = scatterMetricSelect . value || "r2" ;
347+ const metricKey = scatterMetricSelect . value || "nrmse" ;
348+ const metricMeta = scatterMetricByKey [ metricKey ] || { } ;
349+ const lowerIsBetter = ! ! metricMeta . lower_is_better ;
340350 const families = currentScatterFamilies ( ) ;
341351 if ( ! families . length ) {
342- scatterGrid . innerHTML = '<p class="muted">No scatter data for this selection.</p>' ;
352+ scatterGrid . innerHTML = '<p class="muted">No scatter data for this selection (re-collect summaries to populate n_train) .</p>' ;
343353 return ;
344354 }
345355
@@ -357,12 +367,17 @@ <h2>Performance vs test samples</h2>
357367 const innerW = width - pad . l - pad . r ;
358368 const innerH = height - pad . t - pad . b ;
359369
360- const xs = points . map ( p => p . n_samples ) ;
370+ const xs = points . map ( p => p . n_train ) ;
361371 const ys = points . map ( p => p . metrics [ metricKey ] ) ;
362372 const xMin = Math . min ( ...xs ) ;
363373 const xMax = Math . max ( ...xs ) ;
364- const yMin = Math . min ( - 0.2 , ...ys ) ;
365- const yMax = Math . max ( 1.0 , ...ys ) ;
374+ const yPad = lowerIsBetter ? 0.05 : 0.2 ;
375+ const yMin = lowerIsBetter
376+ ? Math . max ( 0 , Math . min ( ...ys ) - yPad )
377+ : Math . min ( - 0.2 , ...ys ) ;
378+ const yMax = lowerIsBetter
379+ ? Math . max ( ...ys ) + yPad
380+ : Math . max ( 1.0 , ...ys ) ;
366381 const xScale = v => pad . l + ( ( v - xMin ) / ( xMax - xMin || 1 ) ) * innerW ;
367382 const yScale = v => pad . t + innerH - ( ( v - yMin ) / ( yMax - yMin || 1 ) ) * innerH ;
368383
@@ -371,7 +386,7 @@ <h2>Performance vs test samples</h2>
371386
372387 let svg = `<svg viewBox="0 0 ${ width } ${ height } " role="img" aria-label="${ fam . family } scatter">` ;
373388 svg += `<rect x="${ pad . l } " y="${ pad . t } " width="${ innerW } " height="${ innerH } " fill="#fff" stroke="#e6eaef"/>` ;
374- if ( yMin < 0 && yMax > 0 ) {
389+ if ( ! lowerIsBetter && yMin < 0 && yMax > 0 ) {
375390 const y0 = yScale ( 0 ) ;
376391 svg += `<line x1="${ pad . l } " y1="${ y0 } " x2="${ pad . l + innerW } " y2="${ y0 } " stroke="#bbb" stroke-dasharray="4 3"/>` ;
377392 }
@@ -381,15 +396,15 @@ <h2>Performance vs test samples</h2>
381396 svg += `<text x="${ xScale ( tx ) } " y="${ height - 8 } " text-anchor="middle" font-size="10" fill="#656d76">${ Math . round ( tx ) } </text>` ;
382397 svg += `<text x="8" y="${ yScale ( ty ) + 3 } " text-anchor="start" font-size="10" fill="#656d76">${ ty . toFixed ( 2 ) } </text>` ;
383398 }
384- svg += `<text x="${ pad . l + innerW / 2 } " y="${ height - 2 } " text-anchor="middle" font-size="11" fill="#1f2328">Test samples </text>` ;
385- svg += `<text x="14" y="${ pad . t + innerH / 2 } " text-anchor="middle" font-size="11" fill="#1f2328" transform="rotate(-90 14 ${ pad . t + innerH / 2 } )">R² </text>` ;
399+ svg += `<text x="${ pad . l + innerW / 2 } " y="${ height - 2 } " text-anchor="middle" font-size="11" fill="#1f2328">Training rows (mean) </text>` ;
400+ svg += `<text x="14" y="${ pad . t + innerH / 2 } " text-anchor="middle" font-size="11" fill="#1f2328" transform="rotate(-90 14 ${ pad . t + innerH / 2 } )">${ scatterMetricLabel ( metricKey ) } </text>` ;
386401
387402 points . forEach ( p => {
388- const cx = xScale ( p . n_samples ) ;
403+ const cx = xScale ( p . n_train ) ;
389404 const cy = yScale ( p . metrics [ metricKey ] ) ;
390405 const col = modelColor ( p . model , modelIdx [ p . model ] ) ;
391406 svg += `<circle cx="${ cx } " cy="${ cy } " r="5" fill="${ col } " fill-opacity="0.85" stroke="#fff" stroke-width="1">
392- <title>${ p . display_name } · ${ p . dataset } \nN =${ p . n_samples } , ${ metricKey } =${ p . metrics [ metricKey ] } </title>
407+ <title>${ p . display_name } · ${ p . dataset } \nTrain =${ p . n_train } , ${ metricKey } =${ p . metrics [ metricKey ] } </title>
393408 </circle>` ;
394409 } ) ;
395410 svg += "</svg>" ;
0 commit comments