@@ -14,6 +14,8 @@ interface DistributionMetric {
1414 method : string ;
1515 metricName : string ;
1616 value : number ;
17+ normalizedValue ?: number ; // Wasserstein distance as percentage of variable range
18+ variableRange ?: number ; // Range of the variable for context
1719}
1820
1921export default function ImputationResults ( { data } : ImputationResultsProps ) {
@@ -22,6 +24,34 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
2224 return data . filter ( d => d . type === 'distribution_distance' ) ;
2325 } , [ data ] ) ;
2426
27+ // Extract variable ranges from distribution_bins data
28+ const variableRanges = useMemo ( ( ) => {
29+ const ranges : Record < string , { min : number ; max : number } > = { } ;
30+ const distributionBins = data . filter ( d => d . type === 'distribution_bins' && d . metric_name === 'histogram_distribution' ) ;
31+
32+ distributionBins . forEach ( d => {
33+ try {
34+ const info = JSON . parse ( d . additional_info ) ;
35+ const variable = d . variable ;
36+
37+ if ( ! ranges [ variable ] ) {
38+ ranges [ variable ] = { min : Infinity , max : - Infinity } ;
39+ }
40+
41+ if ( info . bin_start !== undefined ) {
42+ ranges [ variable ] . min = Math . min ( ranges [ variable ] . min , info . bin_start ) ;
43+ }
44+ if ( info . bin_end !== undefined ) {
45+ ranges [ variable ] . max = Math . max ( ranges [ variable ] . max , info . bin_end ) ;
46+ }
47+ } catch ( e ) {
48+ // Ignore parsing errors
49+ }
50+ } ) ;
51+
52+ return ranges ;
53+ } , [ data ] ) ;
54+
2555 // Group by metric type
2656 const { wassersteinData, klDivergenceData } = useMemo ( ( ) => {
2757 const wasserstein : DistributionMetric [ ] = [ ] ;
@@ -36,21 +66,28 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
3666 } ;
3767
3868 if ( d . metric_name === 'wasserstein_distance' ) {
69+ // Calculate normalized value as percentage of variable range
70+ const range = variableRanges [ d . variable ] ;
71+ if ( range && range . max > range . min ) {
72+ const variableRange = range . max - range . min ;
73+ metric . variableRange = variableRange ;
74+ metric . normalizedValue = ( metric . value / variableRange ) * 100 ;
75+ }
3976 wasserstein . push ( metric ) ;
4077 } else if ( d . metric_name === 'kl_divergence' ) {
4178 klDiv . push ( metric ) ;
4279 }
4380 } ) ;
4481
45- // Sort by value (ascending - lower is better)
46- wasserstein . sort ( ( a , b ) => a . value - b . value ) ;
82+ // Sort by normalized value if available, otherwise by raw value (ascending - lower is better)
83+ wasserstein . sort ( ( a , b ) => ( a . normalizedValue ?? a . value ) - ( b . normalizedValue ?? b . value ) ) ;
4784 klDiv . sort ( ( a , b ) => a . value - b . value ) ;
4885
4986 return {
5087 wassersteinData : wasserstein ,
5188 klDivergenceData : klDiv
5289 } ;
53- } , [ distributionData ] ) ;
90+ } , [ distributionData , variableRanges ] ) ;
5491
5592 const hasWasserstein = wassersteinData . length > 0 ;
5693 const hasKLDivergence = klDivergenceData . length > 0 ;
@@ -59,13 +96,17 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
5996 return null ;
6097 }
6198
62- // Color function based on value quality (lower is better)
63- const getWassersteinColor = ( value : number ) : string => {
64- if ( value < 0.01 ) return '#16a34a' ; // Dark green - excellent
65- if ( value < 0.05 ) return '#22c55e' ; // Green - good
66- if ( value < 0.1 ) return '#eab308' ; // Yellow - moderate
67- if ( value < 0.2 ) return '#f97316' ; // Orange - fair
68- return '#ef4444' ; // Red - poor
99+ // Color function based on normalized value (percentage of range) - lower is better
100+ const getWassersteinColor = ( normalizedValue : number | undefined , rawValue : number ) : string => {
101+ // Use normalized value if available, otherwise fall back to raw thresholds
102+ const value = normalizedValue ?? ( rawValue * 100 ) ; // Assume raw is already a fraction if no range
103+
104+ // Thresholds as percentage of variable range
105+ if ( value < 1 ) return '#16a34a' ; // Dark green - excellent (<1% of range)
106+ if ( value < 3 ) return '#22c55e' ; // Green - good (<3% of range)
107+ if ( value < 5 ) return '#eab308' ; // Yellow - moderate (<5% of range)
108+ if ( value < 10 ) return '#f97316' ; // Orange - fair (<10% of range)
109+ return '#ef4444' ; // Red - poor (>=10% of range)
69110 } ;
70111
71112 const getKLColor = ( value : number ) : string => {
@@ -112,9 +153,9 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
112153 greater differences between imputed and true distributions.
113154 </ p >
114155 < p className = "text-sm text-gray-700" >
115- < strong > Interpretation:</ strong > Values closer to 0 are better. Generally, values below
116- 0.05 indicate good imputation quality, while values above 0.2 suggest significant
117- distributional differences .
156+ < strong > Interpretation:</ strong > Since Wasserstein distance is scale-dependent, quality is assessed
157+ relative to each variable's range. A distance of <1% of the variable range is excellent,
158+ <3% is good, <5% is moderate, <10% is fair, and ≥10% suggests poor distributional match .
118159 </ p >
119160 </ div >
120161
@@ -130,14 +171,21 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
130171 < XAxis type = "number" tick = { { fill : '#000000' } } />
131172 < YAxis type = "category" dataKey = "variable" width = { 90 } tick = { { fill : '#000000' } } />
132173 < Tooltip
133- formatter = { ( value : number ) => [ value . toFixed ( 6 ) , 'Wasserstein Distance' ] }
174+ formatter = { ( value : number , _name : string , props : { payload ?: DistributionMetric } ) => {
175+ const normalizedValue = props . payload ?. normalizedValue ;
176+ const distanceStr = value . toFixed ( 6 ) ;
177+ const pctStr = normalizedValue !== undefined ? ` (${ normalizedValue . toFixed ( 2 ) } % of range)` : '' ;
178+ return [ `${ distanceStr } ${ pctStr } ` , 'Wasserstein Distance' ] ;
179+ } }
180+ contentStyle = { { color : '#000000' } }
181+ labelStyle = { { color : '#000000' } }
134182 />
135183 < Legend wrapperStyle = { { color : '#000000' } } />
136184 < Bar dataKey = "value" name = "Wasserstein Distance" >
137185 { wassersteinData . map ( ( entry , index ) => (
138186 < Cell
139187 key = { `cell-${ index } ` }
140- fill = { getWassersteinColor ( entry . value ) }
188+ fill = { getWassersteinColor ( entry . normalizedValue , entry . value ) }
141189 />
142190 ) ) }
143191 </ Bar >
@@ -156,6 +204,9 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
156204 < th className = "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
157205 Wasserstein Distance
158206 </ th >
207+ < th className = "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
208+ % of Range
209+ </ th >
159210 < th className = "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
160211 Quality Assessment
161212 </ th >
@@ -166,16 +217,19 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
166217 let assessment = '' ;
167218 let assessmentColor = '' ;
168219
169- if ( item . value < 0.01 ) {
220+ // Use normalized value (percentage of range) for assessment
221+ const normalizedValue = item . normalizedValue ?? ( item . value * 100 ) ;
222+
223+ if ( normalizedValue < 1 ) {
170224 assessment = 'Excellent' ;
171225 assessmentColor = 'text-green-700 font-semibold' ;
172- } else if ( item . value < 0.05 ) {
226+ } else if ( normalizedValue < 3 ) {
173227 assessment = 'Good' ;
174228 assessmentColor = 'text-green-600' ;
175- } else if ( item . value < 0.1 ) {
229+ } else if ( normalizedValue < 5 ) {
176230 assessment = 'Moderate' ;
177231 assessmentColor = 'text-yellow-600' ;
178- } else if ( item . value < 0.2 ) {
232+ } else if ( normalizedValue < 10 ) {
179233 assessment = 'Fair' ;
180234 assessmentColor = 'text-orange-600' ;
181235 } else {
@@ -191,6 +245,9 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
191245 < td className = "px-4 py-3 whitespace-nowrap text-sm text-gray-700" >
192246 { item . value . toFixed ( 6 ) }
193247 </ td >
248+ < td className = "px-4 py-3 whitespace-nowrap text-sm text-gray-700" >
249+ { item . normalizedValue !== undefined ? `${ item . normalizedValue . toFixed ( 2 ) } %` : 'N/A' }
250+ </ td >
194251 < td className = { `px-4 py-3 whitespace-nowrap text-sm ${ assessmentColor } ` } >
195252 { assessment }
196253 </ td >
@@ -243,6 +300,8 @@ export default function ImputationResults({ data }: ImputationResultsProps) {
243300 < YAxis type = "category" dataKey = "variable" width = { 90 } tick = { { fill : '#000000' } } />
244301 < Tooltip
245302 formatter = { ( value : number ) => [ value . toFixed ( 6 ) , 'KL-Divergence' ] }
303+ contentStyle = { { color : '#000000' } }
304+ labelStyle = { { color : '#000000' } }
246305 />
247306 < Legend wrapperStyle = { { color : '#000000' } } />
248307 < Bar dataKey = "value" name = "KL-Divergence" >
0 commit comments