Skip to content
This repository was archived by the owner on Mar 12, 2024. It is now read-only.

Commit 0f7d393

Browse files
authored
Support weights for rows (#205)
Co-authored-by: danthe3rd <[email protected]>
1 parent 483d43d commit 0f7d393

File tree

5 files changed

+70
-14
lines changed

5 files changed

+70
-14
lines changed

hiplot/experiment.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ class Experiment(_DictSerializable):
191191
192192
:ivar datapoints: All the measurements we have. One datapoint corresponds to one line in the parallel plot and to one line in the table.
193193
:ivar parameters_definition: Characteristics of the columns (ordering, type, etc...)
194+
:ivar colormap: Colormap to use
195+
:ivar colorby: Default column to color by
196+
:ivar weightcolumn: If rows have different weights, use this column as the weight (default to 1 if not specified)
194197
195198
:Example:
196199
@@ -212,6 +215,7 @@ def __init__(self,
212215
self.parameters_definition = parameters_definition if parameters_definition is not None else defaultdict(ValueDef)
213216
self.colormap = colormap if colormap is not None else "interpolateTurbo"
214217
self.colorby: tp.Optional[str] = None
218+
self.weightcolumn: tp.Optional[str] = None
215219
self._display_data: tp.Dict[str, tp.Dict[str, tp.Any]] = {}
216220
self._compress: bool = False
217221

@@ -394,6 +398,7 @@ def _asdict(self) -> tp.Dict[str, tp.Any]:
394398
"parameters_definition": {k: v._asdict() for k, v in self.parameters_definition.items()},
395399
"colormap": self.colormap,
396400
"colorby": self.colorby,
401+
"weightcolumn": self.weightcolumn,
397402
"display_data": self._display_data,
398403
}
399404
if self._compress:

hiplot/fetchers_demo.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,20 @@ def demo_first_value_nan() -> hip.Experiment:
296296
])
297297

298298

299+
def demo_weighted_rows() -> hip.Experiment:
300+
experiment = hip.Experiment.from_iterable([
301+
{'w': 1.0, 'a': 1, 'b': 1},
302+
{'w': 2.0, 'a': 2, 'b': 1},
303+
{'w': -2.0, 'a': 2, 'b': 1},
304+
{'w': math.inf, 'a': 2, 'b': 2},
305+
{'w': 'not_a_number', 'a': 2, 'b': 3},
306+
{'w': None, 'a': 3, 'b': 3},
307+
{'a': 4, 'b': 3},
308+
])
309+
experiment.weightcolumn = "w"
310+
return experiment
311+
312+
299313
README_DEMOS: t.Dict[str, t.Callable[[], hip.Experiment]] = {
300314
"demo": demo,
301315
"demo_big": lambda: demo(1000),
@@ -318,4 +332,5 @@ def demo_first_value_nan() -> hip.Experiment:
318332
"demo_force_constant_pplot": demo_force_constant_pplot,
319333
"demo_color_interpolate_inverse": demo_color_interpolate_inverse,
320334
"demo_first_value_nan": demo_first_value_nan,
335+
"demo_weighted_rows": demo_weighted_rows,
321336
}

src/component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ export class HiPlot extends React.Component<HiPlotProps, HiPlotState> {
536536
<div className={`${style.hiplot} ${this.state.dark ? style.dark : ""}`}>
537537
<SelectedCountProgressBar {...controlProps} />
538538
<HeaderBar
539+
weightColumn={this.state.experiment ? this.state.experiment.weightcolumn : undefined}
539540
onLoadExperiment={this.loadWithPromise.bind(this)}
540541
persistentState={this.state.persistentState}
541542
dataProvider={this.state.dataProvider}

src/header.tsx

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import style from "./hiplot.scss";
99
import React from "react";
10-
import { HiPlotLoadStatus, IDatasets } from "./types";
10+
import { Datapoint, HiPlotLoadStatus, IDatasets } from "./types";
1111
import { HiPlotDataControlProps, RestoreDataBtn, ExcludeDataBtn, ExportDataCSVBtn, KeepDataBtn } from "./controls";
1212
import { DataProviderClass, DataProviderComponentClass, DataProviderProps} from "./plugin";
1313

@@ -22,6 +22,7 @@ import { PersistentState } from "./lib/savedstate";
2222

2323

2424
interface HeaderBarProps extends IDatasets, HiPlotDataControlProps {
25+
weightColumn?: string;
2526
loadStatus: HiPlotLoadStatus; // Should not allow to load an xp when already loading another xp
2627
persistentState: PersistentState;
2728
onLoadExperiment: (load_promise: Promise<any>) => void;
@@ -33,37 +34,64 @@ interface HeaderBarProps extends IDatasets, HiPlotDataControlProps {
3334
interface HeaderBarState {
3435
isTextareaFocused: boolean;
3536
hasTutorial: boolean;
37+
selectedPct: string;
38+
selectedPctWeighted: string;
3639
};
3740

3841
export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
3942
dataProviderRef = React.createRef<DataProviderClass>();
40-
selected_count_ref: React.RefObject<HTMLElement> = React.createRef();
41-
selected_pct_ref: React.RefObject<HTMLElement> = React.createRef();
42-
total_count_ref: React.RefObject<HTMLElement> = React.createRef();
4343
controls_root_ref: React.RefObject<HTMLDivElement> = React.createRef();
4444

4545
constructor(props: HeaderBarProps) {
4646
super(props);
4747
this.state = {
4848
isTextareaFocused: false,
4949
hasTutorial: false,
50+
selectedPct: '???',
51+
selectedPctWeighted: '???',
5052
};
5153
}
5254
recomputeMetrics() {
53-
if (!this.selected_count_ref.current) {
55+
const newSelectedPct = (100 * this.props.rows_selected.length / this.props.rows_filtered.length).toPrecision(3);
56+
if (newSelectedPct != this.state.selectedPct) {
57+
this.setState({
58+
selectedPct: (100 * this.props.rows_selected.length / this.props.rows_filtered.length).toPrecision(3)
59+
});
60+
}
61+
}
62+
recomputeSelectedWeightedSum() {
63+
if (!this.props.weightColumn) {
64+
this.setState({
65+
selectedPctWeighted: '???',
66+
});
5467
return;
5568
}
56-
const selected_count = this.props.rows_selected.length;
57-
const total_count = this.props.rows_filtered.length;
58-
this.selected_count_ref.current.innerText = '' + selected_count;
59-
this.selected_pct_ref.current.innerText = '' + (100 * selected_count / total_count).toPrecision(3);
60-
this.total_count_ref.current.innerText = '' + total_count;
69+
const getWeight = function(dp: Datapoint): number {
70+
const w = parseFloat(dp[this.props.weightColumn]);
71+
return !isNaN(w) && isFinite(w) && w > 0.0 ? w : 1.0;
72+
}.bind(this);
73+
var totalWeightFiltered = 0.0, totalWeightSelected = 0.0;
74+
this.props.rows_filtered.forEach(function(dp: Datapoint) {
75+
totalWeightFiltered += getWeight(dp);
76+
});
77+
this.props.rows_selected.forEach(function(dp: Datapoint) {
78+
totalWeightSelected += getWeight(dp);
79+
});
80+
const pctage = (100 * totalWeightSelected / totalWeightFiltered);
81+
console.assert(!isNaN(pctage), {"pctage": pctage, "totalWeightFiltered": totalWeightFiltered, "totalWeightSelected": totalWeightSelected});
82+
this.setState({
83+
selectedPctWeighted: pctage.toPrecision(3)
84+
});
6185
}
6286
componentDidMount() {
6387
this.recomputeMetrics();
88+
this.recomputeSelectedWeightedSum();
6489
}
65-
componentDidUpdate() {
90+
componentDidUpdate(prevProps: HeaderBarProps, prevState: HeaderBarState): void {
6691
this.recomputeMetrics();
92+
if (prevProps.weightColumn != this.props.weightColumn || this.props.rows_selected != prevProps.rows_selected || this.props.rows_filtered != prevProps.rows_filtered) {
93+
this.recomputeSelectedWeightedSum();
94+
}
6795
}
6896
onToggleTutorial() {
6997
this.setState(function(prevState, prevProps) {
@@ -107,9 +135,15 @@ export class HeaderBar extends React.Component<HeaderBarProps, HeaderBarState> {
107135
</div>
108136
<div className={style.controlGroup}>
109137
<div style={{"fontFamily": "monospace", "fontSize": "14px"}}>
110-
Selected: <strong ref={this.selected_count_ref} style={{"minWidth": "4em", "textAlign": "right", "display": "inline-block"}}>??</strong>
111-
/<strong ref={this.total_count_ref} style={{"minWidth": "4em", "textAlign": "left", "display": "inline-block"}}>??</strong> (
112-
<span style={{"minWidth": "3em", "textAlign": "right", "display": "inline-block"}} ref={this.selected_pct_ref}>??</span>%)
138+
Selected: <strong style={{"minWidth": "4em", "textAlign": "right", "display": "inline-block"}}>{this.props.rows_selected.length}</strong>
139+
/<strong style={{"minWidth": "4em", "textAlign": "left", "display": "inline-block"}}>{this.props.rows_filtered.length}</strong> (
140+
{!this.props.weightColumn &&
141+
<React.Fragment><span style={{"minWidth": "3em", "textAlign": "right", "display": "inline-block"}}>{this.state.selectedPct}</span>%</React.Fragment>
142+
}
143+
{this.props.weightColumn &&
144+
<React.Fragment><span style={{"minWidth": "3em", "textAlign": "right", "display": "inline-block"}}>{this.state.selectedPctWeighted}</span>% weighted</React.Fragment>
145+
}
146+
)
113147
</div>
114148
</div>
115149
</React.Fragment>

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface HiPlotExperiment { // Mirror of python `hip.Experiment`
4848
parameters_definition?: {[key: string]: HiPlotValueDef},
4949
colormap?: string;
5050
colorby?: string;
51+
weightcolumn?: string;
5152
display_data?: {[key: string]: {[key2: string]: any}},
5253
}
5354

0 commit comments

Comments
 (0)