Commit 9e4520dc authored by Julien Boccard's avatar Julien Boccard
Browse files

Update npca/iplot/__init__.py, npca/iplot/blocks.js, npca/iplot/blocks.py,...

Update npca/iplot/__init__.py, npca/iplot/blocks.js, npca/iplot/blocks.py, npca/iplot/comparisons.js, npca/iplot/comparisons.py, npca/iplot/influences.js, npca/iplot/influences.py, npca/iplot/report.css, npca/iplot/report.js, npca/iplot/report.py, npca/iplot/scores.js, npca/iplot/scores.py, npca/iplot/utils.js, npca/iplot/utils.py files
parent d9eb02f8
function updateBlocks() {
var selected_block_indices = block_map.data[0].selectedpoints
if (!selected_block_indices) {
selected_block_indices = []
}
var selected_blocks = selected_block_indices.map(i => block_map.data[0].customdata[i].index)
shapes = []
for (let cd of block_map.data[0].customdata) {
var rect_data = Object.assign({}, cd.rect_data)
if (selected_blocks.includes(cd.index)) {
rect_data.fillcolor = 'rgb(240,240,240)'
} else {
rect_data.opacity = 0.5
}
shapes.push(rect_data)
}
Plotly.relayout(block_map, {'shapes': shapes})
}
function blockSelectionCallback(data) {
if (data) {
updateAll()
}
}
function blockClickCallback(data) {
if (data) {
var current_selection = block_map.data[0].selectedpoints
if (!current_selection) {
current_selection = []
}
var click_index = data.points[0].pointIndex
if (current_selection.includes(click_index)) {
current_selection = current_selection.filter(i => i != click_index)
} else {
current_selection.push(click_index)
}
var data_update = {
selectedpoints: [current_selection]
}
Plotly.update(block_map, data_update, {})
updateAll()
}
}
function blockLayoutCallback(data) {
var min_size = 10
var max_size = 200
var w = block_map.layout.width
var h = block_map.layout.height
var min_x = block_map.layout.xaxis.range[0]
var max_x = block_map.layout.xaxis.range[1]
var min_y = block_map.layout.yaxis.range[0]
var max_y = block_map.layout.yaxis.range[1]
function x2p(x) {
return w * (x - min_x) / (max_x - min_x)
}
function y2p(y) {
return h * (y - min_y) / (max_y - min_y)
}
var blocks = block_map.data[0]
var d2s = []
for (var i=0; i<blocks.x.length; i++) {
for (var j=i+1; j<blocks.x.length; j++) {
var dx = x2p(blocks.x[i]) - x2p(blocks.x[j])
var dy = y2p(blocks.y[i]) - y2p(blocks.y[j])
d2s.push(dx*dx + dy*dy)
}
}
d2s.sort((a,b) => a - b)
var min_d = Math.sqrt(d2s[0])
var size = Math.min(max_size, Math.max(min_size, min_d * 0.75))
var marker_size = size * 0.9
var line_size = size * 0.1
var data_update = {
'marker.size': marker_size,
'marker.line.width': line_size
}
Plotly.restyle(block_map, data_update)
}
from .utils import get_block_color
def make_block_map(model, monitor):
import plotly.offline as po
import plotly.graph_objs as go
observations = sorted(model.dataset.get_groups(observations=True, variables=False), key=lambda g: (-g.order, g.label))
variables = sorted(model.dataset.get_groups(observations=False, variables=True), key=lambda g: (-g.order, g.label))
block_table = {o: {v: [] for v in variables} for o in observations}
for b in model.dataset.get_blocks():
block_table[b.observations][b.variables].append(b)
max_blocks_by_observation = {o: max([len(block_table[o][v]) for v in variables]) for o in observations}
x_pos = 0
block_index = 0
block_x = []
block_y = []
block_label = []
block_color = []
block_customdata = []
block_hovertext = []
block_indices = {}
for _i_o, o in enumerate(observations):
group_n_blocks = max_blocks_by_observation[o]
y_pos = 0
for _i_v, v in enumerate(variables):
monitor.state(f'Blocks at {o.label}-{v.label}')
for i_b, b in enumerate(block_table[o][v]):
block_index_tag = f'block-{block_index}'
block_plot_set = f' ({b.plot_set.label})' if b.plot_set is not None else ''
block_color_string = get_block_color(b)
block_x.append(x_pos + i_b)
block_y.append(y_pos)
block_label.append(f'<b>{b.key}</b>')
block_color.append(block_color_string)
block_customdata.append(dict(
index=block_index_tag,
name=b.key,
rect_data=dict(
type='rect',
x0=x_pos+i_b-0.45, y0=y_pos-0.45, x1=x_pos+i_b+0.45, y1=y_pos+0.45,
line=dict(
color=block_color_string,
width=4,
),
layer='below',
fillcolor=block_color_string
)
))
block_hovertext.append(
f'<b>{b.key}</b>{block_plot_set}<br>'
f'- {b.observations.label}<br>'
f'- {b.variables.label}<br>'
f'Shape ({b.observations.size()} x {b.variables.size()})<br>'
f'Charge {b.relative_charge} ({b.charge})'
)
block_indices[b] = block_index_tag
block_index += 1
y_pos += 1
x_pos += group_n_blocks + 1
data = [go.Scatter(
name='Block map',
mode='markers+text',
x=block_x,
y=block_y,
text=block_label,
customdata=block_customdata,
hovertext=block_hovertext,
hoverinfo='text',
selected=dict(textfont=dict(color='rgba(0,0,0,1.0)')),
unselected=dict(textfont=dict(color='rgba(0,0,0,0.25)')),
marker=dict(
size=1,
color='rgba(0,0,0,0)',
symbol='square',
line=dict(width=1, color='rgba(0,0,0,0)')
)
)]
layout = go.Layout(
title='Block map',
dragmode='select',
hovermode='closest',
margin=go.layout.Margin(l=20, r=20, b=20, t=50, pad=10),
template='plotly_white',
xaxis=dict(
showgrid=False,
zeroline=False,
showticklabels=False,
linecolor='rgb(180,180,180)',
linewidth=3,
mirror=True
),
yaxis=dict(
showgrid=False,
zeroline=False,
showticklabels=False,
linecolor='rgb(180,180,180)',
linewidth=3,
mirror=True
)
)
config = dict(
modeBarButtons=[['select2d', 'pan2d', 'zoom2d', 'autoScale2d', 'toImage']],
scrollZoom=True,
toImageButtonOptions=dict(format='svg', filename='bpca-blocks'),
displaylogo=False
)
fig = go.Figure(data=data, layout=layout)
div = po.plot(fig, include_plotlyjs=True, output_type='div', config=config)
return div, block_indices
var comparison_variable_group_index = null
var comparison_variable_index = null
function updateComparisons() {
if (comparison_variable_group_index && comparison_variable_index) {
var model_cumulative = comparison_cumulative_model.checked
var show_model = comparison_show_model.checked
var component = parseInt(comparison_model_component.value) - 1
var vi = comparison_variable_index
var selected_block_indices = block_map.data[0].selectedpoints
if (selected_block_indices && selected_block_indices.length > 0) {
var selected_block_tags = selected_block_indices.map(i => block_map.data[0].customdata[i].index)
var all_comparisons = variable_comparisons[comparison_variable_group_index]
var block_indices = []
var block_names = []
var main_bases = []
var main_heights = []
var back_bases = []
var back_heights = []
var means = []
var colors = []
var hover = []
var custom = []
var model_stem_bases = []
var model_stem_heights = []
var model_main_bases = []
var model_main_heights = []
var model_back_bases = []
var model_back_heights = []
var model_hover = []
var variable_name = 'N/A'
var use_all_blocks = !toggle_comparison_selected_blocks.checked
var current_index = 0
var tags = []
for (var tag in all_comparisons) {
if (selected_block_tags.includes(tag) || use_all_blocks) {
tags.push([tag, all_comparisons[tag].observation_order, all_comparisons[tag].block_name])
}
}
function sortTags(a, b) {
if (b[1] == a[1]) {
return b[2] > a[2] ? 1 : (b[2] === a[2] ? 0 : -1)
} else {
return b[1] - a[1]
}
}
tags = tags.sort(sortTags).map((a) => a[0])
for (let tag of tags) {
var c = all_comparisons[tag]
variable_name = c.variable_names[vi]
current_index += 1
var hover_info = '<b>' + c.block_name +
'</b> <i>' + c.plot_set_label + '</i>' +
'<br>Mean: ' + c.mean[vi] +
'<br>Median: ' + c.q50[vi] +
'<br>IQR: ' + (c.q75[vi] - c.q25[vi])
block_indices.push(current_index)
block_names.push(c.block_name)
colors.push(c.color)
main_bases.push(c.q25[vi])
main_heights.push(c.q75[vi] - c.q25[vi])
back_bases.push(c.q05[vi])
back_heights.push(c.q95[vi] - c.q05[vi])
means.push(c.mean[vi])
hover.push(hover_info)
custom.push({
block: c.block_name, mean: c.mean[vi], std: c.std[vi], q05: c.q05[vi], q25: c.q25[vi], q50: c.q50[vi], q75: c.q75[vi], q95: c.q95[vi]
})
model_stem_bases.push(c.model_center[vi])
if (model_cumulative) {
model_stem_heights.push(c.model_cumulative_mean[component][vi] - c.model_center[vi])
model_main_bases.push(c.model_cumulative_q25[component][vi])
model_main_heights.push(c.model_cumulative_q75[component][vi] - c.model_cumulative_q25[component][vi])
model_back_bases.push(c.model_cumulative_q05[component][vi])
model_back_heights.push(c.model_cumulative_q95[component][vi] - c.model_cumulative_q05[component][vi])
model_hover.push('<b>Model contribution</b> <i>' + c.block_name + '</i><br>Components 1 to ' + (component+1))
} else {
model_stem_heights.push(c.model_mean[component][vi] - c.model_center[vi])
model_main_bases.push(c.model_q25[component][vi])
model_main_heights.push(c.model_q75[component][vi] - c.model_q25[component][vi])
model_back_bases.push(c.model_q05[component][vi])
model_back_heights.push(c.model_q95[component][vi] - c.model_q05[component][vi])
model_hover.push('<b>Model contribution</b> <i>' + c.block_name + '</i><br>Component ' + (component+1))
}
}
var data_bar_offset = show_model ? -0.5 : -0.4
var data_bar_width = show_model ? 0.6 : 0.8
data_update = [{ // full data spread
type: 'bar',
base: back_bases,
x: block_indices,
y: back_heights,
width: data_bar_width,
offset: data_bar_offset,
hovertext: hover,
hoverinfo: 'text',
customdata: custom,
marker: {
opacity: 0.25,
color: colors
}
}, {
type: 'bar', // interquartile
base: main_bases,
y: main_heights,
x: block_indices,
width: data_bar_width,
offset: data_bar_offset,
hoverinfo: 'skip',
marker: {
color: colors,
}
}]
if (show_model) {
data_update.push(...[
{ // model full spread
type: 'bar',
base: model_back_bases,
y: model_back_heights,
x: block_indices,
width: 0.2,
offset: +0.15,
hovertext: model_hover,
hoverinfo: 'text',
marker: {
color: 'rgb(200,200,200)',
}
}, { // model interquartile
type: 'bar',
base: model_main_bases,
y: model_main_heights,
x: block_indices,
width: 0.2,
offset: +0.15,
hoverinfo: 'skip',
marker: {
color: 'rgb(150,150,150)',
}
}, { // model stem
type: 'bar',
base: model_stem_bases,
y: model_stem_heights,
x: block_indices,
width: 0.05,
offset: +0.225,
hoverinfo: 'skip',
marker: {
color: 'rgb(0,0,0)',
}
}
])
}
var old_layout = variable_comparison_bars.layout
var old_config = variable_comparison_bars.config
var title = 'Variable comparison: ' + variable_name
if (model_cumulative) {
title += '<br>Components 1 - ' + (component + 1)
} else {
title += '<br>Component ' + (component + 1)
}
old_layout.title = title
old_layout.xaxis.tickvals = block_indices
old_layout.xaxis.ticktext = block_names
Plotly.react(variable_comparison_bars, data_update, old_layout, old_config)
}
}
}
function downloadComparison() {
var table = [[variable_comparison_bars.layout.title.text], ['Block', 'Mean', 'Std', 'P05%', 'P25%', 'Median', 'P75%', 'P95%']]
var cdata = variable_comparison_bars.data[0].customdata
if (cdata) {
for (let c of cdata) {
table.push([c.block, c.mean, c.std, c.q05, c.q25, c.q50, c.q75, c.q95])
}
make_download(table, 'bpca-variable-comparison.csv')
}
}
import numpy as np
from .utils import prepare_numbers, get_block_color
def make_comparison_bars(model, monitor, block_indices, variable_group_indices, selected_numpy_variable_indices):
import plotly.offline as po
import plotly.graph_objs as go
variable_groups = model.dataset.get_groups(observations=False, variables=True)
blocks = model.dataset.get_blocks()
blocks_by_variable_group = {v: [] for v in variable_groups}
for b in blocks:
blocks_by_variable_group[b.variables].append(b)
variable_comparison_dict = {}
for v in variable_groups:
by_block = {}
variable_comparison_dict[variable_group_indices[v]] = by_block
for b in blocks_by_variable_group[v]:
monitor.state(f'Variable group {v.label} - block {b.key}')
for_block = {}
by_block[block_indices[b]] = for_block
raw_data = b.data * b.scale + b.center
model_data = [component.block_reconstruct(b) for component in model.components]
model_cumulative_data = [model_data[0]]
for m_d in model_data[1:]:
model_cumulative_data.append(model_cumulative_data[-1] + m_d)
model_data = [m_d * b.scale + b.center for m_d in model_data]
model_cumulative_data = [m_d * b.scale + b.center for m_d in model_cumulative_data]
raw_data = raw_data[:, selected_numpy_variable_indices[v]]
model_data = [d[:, selected_numpy_variable_indices[v]] for d in model_data]
model_cumulative_data = [d[:, selected_numpy_variable_indices[v]] for d in model_cumulative_data]
plot_set_label = '?'
if b.plot_set is not None:
plot_set_label = b.plot_set.label
pn = prepare_numbers
for_block['variable_names'] = [v.ids[i] for i in selected_numpy_variable_indices[v]]
for_block['observation_order'] = b.observations.order
for_block['block_name'] = b.key
for_block['plot_set_label'] = plot_set_label
for_block['color'] = get_block_color(b)
for_block['mean'] = pn(np.mean(raw_data, axis=0))
for_block['std'] = pn(np.std(raw_data, axis=0))
for_block['q05'] = pn(np.quantile(raw_data, 0.05, axis=0))
for_block['q25'] = pn(np.quantile(raw_data, 0.25, axis=0))
for_block['q50'] = pn(np.quantile(raw_data, 0.50, axis=0))
for_block['q75'] = pn(np.quantile(raw_data, 0.75, axis=0))
for_block['q95'] = pn(np.quantile(raw_data, 0.95, axis=0))
for_block['model_center'] = pn(b.center[0, selected_numpy_variable_indices[v]])
for_block['model_mean'] = [pn(np.mean(data, axis=0)) for data in model_data]
for_block['model_q05'] = [pn(np.quantile(data, 0.05, axis=0)) for data in model_data]
for_block['model_q25'] = [pn(np.quantile(data, 0.25, axis=0)) for data in model_data]
for_block['model_q50'] = [pn(np.quantile(data, 0.50, axis=0)) for data in model_data]
for_block['model_q75'] = [pn(np.quantile(data, 0.75, axis=0)) for data in model_data]
for_block['model_q95'] = [pn(np.quantile(data, 0.95, axis=0)) for data in model_data]
for_block['model_cumulative_mean'] = [pn(np.mean(data, axis=0)) for data in model_cumulative_data]
for_block['model_cumulative_q05'] = [pn(np.quantile(data, 0.05, axis=0)) for data in model_cumulative_data]
for_block['model_cumulative_q25'] = [pn(np.quantile(data, 0.25, axis=0)) for data in model_cumulative_data]
for_block['model_cumulative_q50'] = [pn(np.quantile(data, 0.50, axis=0)) for data in model_cumulative_data]
for_block['model_cumulative_q75'] = [pn(np.quantile(data, 0.75, axis=0)) for data in model_cumulative_data]
for_block['model_cumulative_q95'] = [pn(np.quantile(data, 0.95, axis=0)) for data in model_cumulative_data]
data = [
go.Box(
x=[],
y=[]
)
]
layout = go.Layout(
barmode='overlay',
showlegend=False,
title='Variable comparison',
dragmode='pan',
hovermode='closest',
margin=go.layout.Margin(l=50, r=50, b=50, pad=10),
template='plotly_white',
xaxis=dict(
type='category',
tickmode='array',
automargin=True
),
yaxis=dict(
rangemode='tozero',
automargin=True,
)
)
config = dict(
modeBarButtons=[['pan2d', 'autoScale2d', 'toImage']],
toImageButtonOptions=dict(format='svg', filename='bpca-variable-comparison'),
scrollZoom=True,
displaylogo=False
)
fig = go.Figure(data=data, layout=layout)
div = po.plot(fig, include_plotlyjs=False, output_type='div', config=config)
return div, variable_comparison_dict
function updateInfluences() {
if (lock_influences.checked) {
return;
}
var n_vars = parseInt(influences_shown_variables.value)
var component = parseInt(component_main_field.value) - 1
var selected_block_indices = block_map.data[0].selectedpoints
if (selected_block_indices && selected_block_indices.length > 0) {
var selected_blocks = selected_block_indices.map(i => block_map.data[0].customdata[i].index)
var selected_variable_group = null
var selected_variable_group_index = null
for (var tag in variable_groups) {
var group_info = variable_groups[tag]
if (group_info.blocks.includes(selected_blocks[0])) {
selected_variable_group_index = tag
selected_variable_group = group_info
break
}
}
selected_blocks = selected_blocks.filter(b => selected_variable_group.blocks.includes(b))
var loading_is_positive = selected_variable_group.loadings[component].map(l => l>=0)
var passes_regex = selected_variable_group.variable_names.map(n => true)
if (influences_name_filter.value.length > 0) {
var re = RegExp(influences_name_filter.value)
passes_regex = selected_variable_group.variable_names.map(n => re.test(n.toLowerCase().replace(/\W/g, '')))
}
var influence_over_selected_blocks = selected_variable_group.loadings[component].map(x => 0)
for (let sel_block of selected_blocks) {
for (var i=0; i<influence_over_selected_blocks.length; i++) {
influence_over_selected_blocks[i] += influences[component][sel_block].infs[i]
}
}
var influence_indices = influence_over_selected_blocks.map((inf, i) => [inf, i])
var max_influence = Math.max(...influence_over_selected_blocks)
function sortIndices(a, b) {
var inf_a = a[0]
var inf_b = b[0]
var ind_a = a[1]
var ind_b = b[1]
if (passes_regex[ind_b] && !passes_regex[ind_a]) {
return +1
} else if (passes_regex[ind_a] && !passes_regex[ind_b]) {
return -1
} else {
return inf_b - inf_a
}
}
var positive_indices = influence_indices.filter(
inf_i => loading_is_positive[inf_i[1]]
).sort(sortIndices).slice(0, n_vars).map(inf_i => inf_i[1])
var negative_indices = influence_indices.filter(
inf_i => !loading_is_positive[inf_i[1]]
).sort(sortIndices).slice(0, n_vars).map(inf_i => inf_i[1])
var x_range = positive_indices.map((v, i) => i)
var data_update = []
function format_influence(inf_value) {
var abs_value = Math.abs(inf_value)
if (abs_value < 1e-4) {
return (1e6 * inf_value) + ' (ppm)'
} else if (abs_value < 1e-2) {
return (1e3 * inf_value) + ' (\u2030)'
} else {
return (1e2 * inf_value) + ' (%)'
}
}
function generate_trace(trace_block, trace_is_positive) {
var indices = trace_is_positive ? positive_indices : negative_indices
var all_infs = influences[component][trace_block].infs
var infs = indices.map(i => all_infs[i])
function make_info(index) {
var base = selected_variable_group.block_names[trace_block] +
': ' + selected_variable_group.variable_names[index] +
'<br>' + format_influence(all_infs[index])
for (var key in selected_variable_group.metadata) {
base += '<br> - ' + key + ': ' + selected_variable_group.metadata[key][index]
}
return base
}
var variable_info = indices.map(make_info)
var custom_info = indices.map(i => ({
variable_group_index: selected_variable_group_index,
variable_index: i,
block: selected_variable_group.block_names[trace_block],
variable: selected_variable_group.variable_names[i],
loading: selected_variable_group.loadings[component][i],
influence: all_infs[i],
sign: trace_is_positive ? 'Positive' : 'Negative'
}))
var xaxis_label = trace_is_positive ? 'x' : 'x2'
var yaxis_label = trace_is_positive ? 'y' : 'y2'
return {
type: 'bar',
x: x_range,
y: infs,
xaxis: xaxis_label,
yaxis: yaxis_label,
customdata: custom_info,
hovertext: variable_info,