- Add ConvergencePlot component with running best, statistics, gradient fill - Add ParameterImportanceChart with Pearson correlation analysis - Add StudyReportViewer with KaTeX math rendering and full markdown support - Update pruning endpoint to query Optuna database directly - Add /report endpoint for STUDY_REPORT.md files - Fix chart data transformation for single/multi-objective studies - Update Protocol 13 documentation with new components - Update generate-report skill with dashboard integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
185 lines
6.2 KiB
TypeScript
185 lines
6.2 KiB
TypeScript
/**
|
|
* Parameter Importance Chart
|
|
* Shows which design parameters have the most impact on objectives
|
|
* Uses correlation analysis between parameters and objective values
|
|
*/
|
|
|
|
import { useMemo } from 'react';
|
|
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts';
|
|
|
|
interface Trial {
|
|
trial_number: number;
|
|
values: number[];
|
|
params: Record<string, number>;
|
|
}
|
|
|
|
interface DesignVariable {
|
|
name: string;
|
|
parameter?: string;
|
|
unit?: string;
|
|
}
|
|
|
|
interface ParameterImportanceChartProps {
|
|
trials: Trial[];
|
|
designVariables: DesignVariable[];
|
|
objectiveIndex?: number;
|
|
objectiveName?: string;
|
|
}
|
|
|
|
// Calculate Pearson correlation coefficient
|
|
function pearsonCorrelation(x: number[], y: number[]): number {
|
|
if (x.length !== y.length || x.length < 2) return 0;
|
|
|
|
const n = x.length;
|
|
const sumX = x.reduce((a, b) => a + b, 0);
|
|
const sumY = y.reduce((a, b) => a + b, 0);
|
|
const sumXY = x.reduce((acc, xi, i) => acc + xi * y[i], 0);
|
|
const sumX2 = x.reduce((acc, xi) => acc + xi * xi, 0);
|
|
const sumY2 = y.reduce((acc, yi) => acc + yi * yi, 0);
|
|
|
|
const numerator = n * sumXY - sumX * sumY;
|
|
const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
|
|
|
|
if (denominator === 0) return 0;
|
|
return numerator / denominator;
|
|
}
|
|
|
|
export function ParameterImportanceChart({
|
|
trials,
|
|
designVariables,
|
|
objectiveIndex = 0,
|
|
objectiveName = 'Objective'
|
|
}: ParameterImportanceChartProps) {
|
|
const importanceData = useMemo(() => {
|
|
if (!trials || trials.length < 3 || !designVariables || designVariables.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
// Extract objective values
|
|
const objectiveValues = trials
|
|
.filter(t => t.values && t.values.length > objectiveIndex)
|
|
.map(t => t.values[objectiveIndex]);
|
|
|
|
if (objectiveValues.length < 3) return [];
|
|
|
|
// Calculate correlation for each parameter
|
|
const importances = designVariables.map(dv => {
|
|
const paramName = dv.parameter || dv.name;
|
|
const paramValues = trials
|
|
.filter(t => t.params && paramName in t.params && t.values && t.values.length > objectiveIndex)
|
|
.map(t => t.params[paramName]);
|
|
|
|
const corrObjectiveValues = trials
|
|
.filter(t => t.params && paramName in t.params && t.values && t.values.length > objectiveIndex)
|
|
.map(t => t.values[objectiveIndex]);
|
|
|
|
if (paramValues.length < 3) return { name: dv.name, importance: 0, correlation: 0 };
|
|
|
|
const correlation = pearsonCorrelation(paramValues, corrObjectiveValues);
|
|
// Use absolute correlation as importance (sign indicates direction)
|
|
const importance = Math.abs(correlation);
|
|
|
|
return {
|
|
name: dv.name,
|
|
importance: importance * 100, // Convert to percentage
|
|
correlation,
|
|
direction: correlation > 0 ? 'positive' : 'negative'
|
|
};
|
|
});
|
|
|
|
// Sort by importance (descending)
|
|
return importances.sort((a, b) => b.importance - a.importance);
|
|
}, [trials, designVariables, objectiveIndex]);
|
|
|
|
if (importanceData.length === 0) {
|
|
return (
|
|
<div className="bg-dark-700 rounded-lg p-6 border border-dark-500 shadow-sm">
|
|
<h3 className="text-lg font-semibold mb-4 text-dark-100">Parameter Importance</h3>
|
|
<div className="h-64 flex items-center justify-center text-dark-400">
|
|
Need at least 3 trials for correlation analysis
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Color based on correlation direction
|
|
const getBarColor = (entry: typeof importanceData[0]) => {
|
|
if (entry.correlation > 0) {
|
|
// Positive correlation (increasing param increases objective) - red for minimization
|
|
return '#f87171';
|
|
} else {
|
|
// Negative correlation (increasing param decreases objective) - green for minimization
|
|
return '#34d399';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="bg-dark-700 rounded-lg p-6 border border-dark-500 shadow-sm">
|
|
<div className="mb-4">
|
|
<h3 className="text-lg font-semibold text-dark-100">Parameter Importance</h3>
|
|
<p className="text-sm text-dark-400 mt-1">
|
|
Correlation with {objectiveName} ({trials.length} trials)
|
|
</p>
|
|
</div>
|
|
|
|
<div className="h-72">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<BarChart
|
|
data={importanceData}
|
|
layout="vertical"
|
|
margin={{ top: 5, right: 30, left: 120, bottom: 5 }}
|
|
>
|
|
<XAxis
|
|
type="number"
|
|
domain={[0, 100]}
|
|
tickFormatter={(v) => `${v.toFixed(0)}%`}
|
|
tick={{ fill: '#94a3b8', fontSize: 11 }}
|
|
stroke="#334155"
|
|
/>
|
|
<YAxis
|
|
type="category"
|
|
dataKey="name"
|
|
width={110}
|
|
tick={{ fill: '#94a3b8', fontSize: 11 }}
|
|
stroke="#334155"
|
|
/>
|
|
<Tooltip
|
|
formatter={(value: number, _name: string, props: any) => {
|
|
const corr = props.payload.correlation;
|
|
return [
|
|
`${value.toFixed(1)}% (r=${corr.toFixed(3)})`,
|
|
'Importance'
|
|
];
|
|
}}
|
|
contentStyle={{
|
|
backgroundColor: '#1e293b',
|
|
border: '1px solid #334155',
|
|
borderRadius: '8px',
|
|
boxShadow: '0 4px 6px rgba(0,0,0,0.3)'
|
|
}}
|
|
labelStyle={{ color: '#e2e8f0' }}
|
|
/>
|
|
<Bar dataKey="importance" radius={[0, 4, 4, 0]}>
|
|
{importanceData.map((entry, index) => (
|
|
<Cell key={index} fill={getBarColor(entry)} />
|
|
))}
|
|
</Bar>
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* Legend */}
|
|
<div className="flex gap-6 justify-center mt-4 text-sm border-t border-dark-500 pt-4">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 rounded" style={{ backgroundColor: '#34d399' }} />
|
|
<span className="text-dark-300">Negative correlation (helps minimize)</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-4 rounded" style={{ backgroundColor: '#f87171' }} />
|
|
<span className="text-dark-300">Positive correlation (hurts minimize)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|