source: branches/2971_named_intervals/HeuristicLab.Problems.DataAnalysis.Views/3.4/Regression/RegressionSolutionPartialDependencePlotView.cs @ 16638

Last change on this file since 16638 was 16638, checked in by gkronber, 7 months ago

#2971: merged r16527:16625 from trunk/HeuristicLab.Problems.DataAnalysis.Views to branch/HeuristicLab.Problems.DataAnalysis.Views

File size: 27.1 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections;
24using System.Collections.Generic;
25using System.Drawing;
26using System.Globalization;
27using System.Linq;
28using System.Threading.Tasks;
29using System.Windows.Forms;
30using HeuristicLab.Common;
31using HeuristicLab.MainForm;
32using HeuristicLab.Visualization.ChartControlsExtensions;
33
34namespace HeuristicLab.Problems.DataAnalysis.Views {
35  [View("Partial Dependence Plots")]
36  [Content(typeof(IRegressionSolution))]
37  public partial class RegressionSolutionPartialDependencePlotView : DataAnalysisSolutionEvaluationView {
38    private readonly Dictionary<string, IPartialDependencePlot> partialDependencePlots;
39    private readonly Dictionary<string, DensityChart> densityCharts;
40    private readonly Dictionary<string, Panel> groupingPanels;
41    private ModifiableDataset sharedFixedVariables;
42
43    private const int Points = 200;
44    private int MaxColumns = 4;
45
46    private IEnumerable<string> VisibleVariables {
47      get {
48        foreach (ListViewItem item in variableListView.CheckedItems)
49          yield return item.Text;
50      }
51    }
52    private IEnumerable<IPartialDependencePlot> VisiblePartialDependencePlots {
53      get { return VisibleVariables.Select(v => partialDependencePlots[v]); }
54    }
55    private IEnumerable<DensityChart> VisibleDensityCharts {
56      get { return VisibleVariables.Select(v => densityCharts[v]); }
57    }
58    private IEnumerable<Panel> VisibleChartsPanels {
59      get { return VisibleVariables.Select(v => groupingPanels[v]); }
60    }
61
62    public RegressionSolutionPartialDependencePlotView() {
63      InitializeComponent();
64      partialDependencePlots = new Dictionary<string, IPartialDependencePlot>();
65      densityCharts = new Dictionary<string, DensityChart>();
66      groupingPanels = new Dictionary<string, Panel>();
67
68      limitView.Content = new DoubleLimit(0, 1);
69      limitView.Content.ValueChanged += limit_ValueChanged;
70
71      densityComboBox.SelectedIndex = 1; // select Training
72
73      // Avoid additional horizontal scrollbar
74      var vertScrollWidth = SystemInformation.VerticalScrollBarWidth;
75      scrollPanel.Padding = new Padding(0, 0, vertScrollWidth, 0);
76      scrollPanel.AutoScroll = true;
77    }
78
79    public new IRegressionSolution Content {
80      get { return (IRegressionSolution)base.Content; }
81      set { base.Content = value; }
82    }
83
84    protected override void RegisterContentEvents() {
85      base.RegisterContentEvents();
86      Content.ModelChanged += solution_ModelChanged;
87    }
88
89    protected override void DeregisterContentEvents() {
90      Content.ModelChanged -= solution_ModelChanged;
91      base.DeregisterContentEvents();
92    }
93
94    protected override void OnContentChanged() {
95      base.OnContentChanged();
96      if (Content == null) return;
97      var problemData = Content.ProblemData;
98
99      if (sharedFixedVariables != null) {
100        sharedFixedVariables.ItemChanged -= SharedFixedVariables_ItemChanged;
101        sharedFixedVariables.Reset -= SharedFixedVariables_Reset;
102      }
103
104      // Init Y-axis range
105      double min = double.MaxValue, max = double.MinValue;
106      var trainingTarget = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);
107      foreach (var t in trainingTarget) {
108        if (t < min) min = t;
109        if (t > max) max = t;
110      }
111      double range = max - min;
112      const double scale = 1.0 / 3.0;
113      double axisMin, axisMax, axisInterval;
114      ChartUtil.CalculateAxisInterval(min - scale * range, max + scale * range, 5, out axisMin, out axisMax, out axisInterval);
115      automaticYAxisCheckBox.Checked = false;
116      limitView.ReadOnly = false;
117      limitView.Content.Lower = axisMin;
118      limitView.Content.Upper = axisMax;
119
120      // create dataset of problemData input variables and model input variables
121      // necessary workaround to have the variables in the occuring order
122      var inputvariables =
123        new HashSet<string>(Content.ProblemData.AllowedInputVariables.Union(Content.Model.VariablesUsedForPrediction));
124      var allowedInputVariables =
125        Content.ProblemData.Dataset.VariableNames.Where(v => inputvariables.Contains(v)).ToList();
126
127      var doubleVariables = allowedInputVariables.Where(problemData.Dataset.VariableHasType<double>);
128      var doubleVariableValues = (IEnumerable<IList>)doubleVariables.Select(x => new List<double> {
129        problemData.Dataset.GetDoubleValue(x, 0)
130      });
131
132      var factorVariables = allowedInputVariables.Where(problemData.Dataset.VariableHasType<string>);
133      var factorVariableValues = (IEnumerable<IList>)factorVariables.Select(x => new List<string> {
134        problemData.Dataset.GetStringValue(x, 0)
135      });
136
137      sharedFixedVariables = new ModifiableDataset(doubleVariables.Concat(factorVariables), doubleVariableValues.Concat(factorVariableValues));
138      variableValuesModeComboBox.SelectedItem = "Median"; // triggers UpdateVariableValue and changes shardFixedVariables
139
140      // create controls
141      partialDependencePlots.Clear();
142      densityCharts.Clear();
143      groupingPanels.Clear();
144      foreach (var variableName in doubleVariables) {
145        var plot = CreatePartialDependencePlot(variableName, sharedFixedVariables);
146        partialDependencePlots.Add(variableName, plot);
147
148        var densityChart = new DensityChart() {
149          Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right,
150          Margin = Padding.Empty,
151          Height = 12,
152          Visible = false,
153          Top = (int)(plot.Height * 0.1),
154        };
155        densityCharts.Add(variableName, densityChart);
156
157        plot.ZoomChanged += (o, e) => {
158          var pdp = (PartialDependencePlot)o;
159          var density = densityCharts[pdp.FreeVariable];
160          density.Visible = densityComboBox.SelectedIndex != 0 && !pdp.IsZoomed;
161          if (density.Visible)
162            UpdateDensityChart(density, pdp.FreeVariable);
163        };
164        plot.SizeChanged += (o, e) => {
165          var pdp = (PartialDependencePlot)o;
166          var density = densityCharts[pdp.FreeVariable];
167          density.Top = (int)(pdp.Height * 0.1);
168        };
169
170        // Initially, the inner plot areas are not initialized for hidden charts (scrollpanel, ...)
171        // This event handler listens for the paint event once (where everything is already initialized) to do some manual layouting.
172        plot.ChartPostPaint += OnPartialDependencePlotPostPaint;
173
174        var panel = new Panel() {
175          Dock = DockStyle.Fill,
176          Margin = Padding.Empty,
177          BackColor = Color.White
178        };
179
180        panel.Controls.Add(densityChart);
181        panel.Controls.Add(plot);
182        groupingPanels.Add(variableName, panel);
183      }
184      foreach (var variableName in factorVariables) {
185        var plot = CreateFactorPartialDependencePlot(variableName, sharedFixedVariables);
186        partialDependencePlots.Add(variableName, plot);
187
188        var densityChart = new DensityChart() {
189          Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right,
190          Margin = Padding.Empty,
191          Height = 12,
192          Visible = false,
193          Top = (int)(plot.Height * 0.1),
194        };
195        densityCharts.Add(variableName, densityChart);
196        plot.ZoomChanged += (o, e) => {
197          var pdp = (FactorPartialDependencePlot)o;
198          var density = densityCharts[pdp.FreeVariable];
199          density.Visible = densityComboBox.SelectedIndex != 0 && !pdp.IsZoomed;
200          if (density.Visible)
201            UpdateDensityChart(density, pdp.FreeVariable);
202        };
203        plot.SizeChanged += (o, e) => {
204          var pdp = (FactorPartialDependencePlot)o;
205          var density = densityCharts[pdp.FreeVariable];
206          density.Top = (int)(pdp.Height * 0.1);
207        };
208
209        // Initially, the inner plot areas are not initialized for hidden charts (scrollpanel, ...)
210        // This event handler listens for the paint event once (where everything is already initialized) to do some manual layouting.
211        plot.ChartPostPaint += OnFactorPartialDependencePlotPostPaint;
212
213        var panel = new Panel() {
214          Dock = DockStyle.Fill,
215          Margin = Padding.Empty,
216          BackColor = Color.White
217        };
218
219        panel.Controls.Add(densityChart);
220        panel.Controls.Add(plot);
221        groupingPanels.Add(variableName, panel);
222      }
223      // update variable list
224      variableListView.ItemChecked -= variableListView_ItemChecked;
225      variableListView.Items.Clear();
226      foreach (var variable in allowedInputVariables)
227        variableListView.Items.Add(key: variable, text: variable, imageIndex: 0);
228
229      foreach (var variable in Content.Model.VariablesUsedForPrediction)
230        variableListView.Items[variable].Checked = true;
231      variableListView.ItemChecked += variableListView_ItemChecked;
232
233      sharedFixedVariables.ItemChanged += SharedFixedVariables_ItemChanged;
234      sharedFixedVariables.Reset += SharedFixedVariables_Reset;
235
236      rowNrNumericUpDown.Maximum = Content.ProblemData.Dataset.Rows - 1;
237
238      RecalculateAndRelayoutCharts();
239    }
240
241    private void SharedFixedVariables_ItemChanged(object sender, EventArgs<int, int> e) {
242      SharedFixedVariablesChanged();
243    }
244    private void SharedFixedVariables_Reset(object sender, EventArgs e) {
245      SharedFixedVariablesChanged();
246    }
247    private void SharedFixedVariablesChanged() {
248      if (!setVariableValues) // set mode to "nothing" if change was not initiated from a "mode change"
249        variableValuesModeComboBox.SelectedIndex = -1;
250
251      double yValue = Content.Model.GetEstimatedValues(sharedFixedVariables, new[] { 0 }).Single();
252      string title = Content.ProblemData.TargetVariable + ": " + yValue.ToString("G5", CultureInfo.CurrentCulture);
253      foreach (var chart in partialDependencePlots.Values) {
254        if (!string.IsNullOrEmpty(chart.YAxisTitle)) { // only show title for first column in grid
255          chart.YAxisTitle = title;
256        }
257      }
258    }
259
260    private void OnPartialDependencePlotPostPaint(object o, EventArgs e) {
261      var plot = (PartialDependencePlot)o;
262      var density = densityCharts[plot.FreeVariable];
263
264      density.Width = plot.Width;
265
266      var gcPlotPosition = plot.InnerPlotPosition;
267      density.Left = (int)(gcPlotPosition.X / 100.0 * plot.Width);
268      density.Width = (int)(gcPlotPosition.Width / 100.0 * plot.Width);
269      plot.UpdateTitlePosition();
270
271      // removed after succesful layouting due to performance reasons
272      if (gcPlotPosition.Width != 0)
273        plot.ChartPostPaint -= OnPartialDependencePlotPostPaint;
274    }
275
276    private void OnFactorPartialDependencePlotPostPaint(object o, EventArgs e) {
277      var plot = (FactorPartialDependencePlot)o;
278      var density = densityCharts[plot.FreeVariable];
279
280      density.Width = plot.Width;
281
282      var gcPlotPosition = plot.InnerPlotPosition;
283      density.Left = (int)(gcPlotPosition.X / 100.0 * plot.Width);
284      density.Width = (int)(gcPlotPosition.Width / 100.0 * plot.Width);
285      plot.UpdateTitlePosition();
286
287      // removed after succesful layouting due to performance reasons
288      if (gcPlotPosition.Width != 0)
289        plot.ChartPostPaint -= OnFactorPartialDependencePlotPostPaint;
290    }
291
292    private async void RecalculateAndRelayoutCharts() {
293      foreach (var variable in VisibleVariables) {
294        var plot = partialDependencePlots[variable];
295        await plot.RecalculateAsync(false, false);
296      }
297      partialDependencePlotTableLayout.SuspendLayout();
298      SetupYAxis();
299      ReOrderControls();
300      SetStyles();
301      partialDependencePlotTableLayout.ResumeLayout();
302      partialDependencePlotTableLayout.Refresh();
303      foreach (var variable in VisibleVariables) {
304        DensityChart densityChart;
305        if (densityCharts.TryGetValue(variable, out densityChart)) {
306          UpdateDensityChart(densityChart, variable);
307        }
308      }
309    }
310    private PartialDependencePlot CreatePartialDependencePlot(string variableName, ModifiableDataset sharedFixedVariables) {
311      var plot = new PartialDependencePlot {
312        Dock = DockStyle.Fill,
313        Margin = Padding.Empty,
314        ShowLegend = false,
315        ShowCursor = true,
316        ShowConfigButton = false,
317        YAxisTicks = 5,
318      };
319      plot.VariableValueChanged += async (o, e) => {
320        var recalculations = VisiblePartialDependencePlots
321          .Except(new[] { (IPartialDependencePlot)o })
322          .Select(async chart => {
323            await chart.RecalculateAsync(updateOnFinish: false, resetYAxis: false);
324          }).ToList();
325        await Task.WhenAll(recalculations);
326
327        if (recalculations.All(t => t.IsCompleted))
328          SetupYAxis();
329      };
330      plot.Configure(new[] { Content }, sharedFixedVariables, variableName, Points);
331      plot.SolutionAdded += partialDependencePlot_SolutionAdded;
332      plot.SolutionRemoved += partialDependencePlot_SolutionRemoved;
333      return plot;
334    }
335    private FactorPartialDependencePlot CreateFactorPartialDependencePlot(string variableName, ModifiableDataset sharedFixedVariables) {
336      var plot = new FactorPartialDependencePlot {
337        Dock = DockStyle.Fill,
338        Margin = Padding.Empty,
339        ShowLegend = false,
340        ShowCursor = true,
341        YAxisTicks = 5,
342      };
343      plot.VariableValueChanged += async (o, e) => {
344        var recalculations = VisiblePartialDependencePlots
345          .Except(new[] { (FactorPartialDependencePlot)o })
346          .Select(async chart => {
347            await chart.RecalculateAsync(updateOnFinish: false, resetYAxis: false);
348          }).ToList();
349        await Task.WhenAll(recalculations);
350
351        if (recalculations.All(t => t.IsCompleted))
352          SetupYAxis();
353      };
354      var variableValues = Content.ProblemData.Dataset.GetStringValues(variableName).Distinct().OrderBy(n => n).ToList();
355      plot.Configure(new[] { Content }, sharedFixedVariables, variableName, variableValues);
356      plot.SolutionAdded += partialDependencePlot_SolutionAdded;
357      plot.SolutionRemoved += partialDependencePlot_SolutionRemoved;
358      return plot;
359    }
360    private void SetupYAxis() {
361      double axisMin, axisMax;
362      if (automaticYAxisCheckBox.Checked) {
363        double min = double.MaxValue, max = double.MinValue;
364        foreach (var chart in VisiblePartialDependencePlots) {
365          if (chart.YMin < min) min = chart.YMin;
366          if (chart.YMax > max) max = chart.YMax;
367        }
368
369        double axisInterval;
370        ChartUtil.CalculateAxisInterval(min, max, 5, out axisMin, out axisMax, out axisInterval);
371      } else {
372        axisMin = limitView.Content.Lower;
373        axisMax = limitView.Content.Upper;
374      }
375
376      foreach (var chart in VisiblePartialDependencePlots) {
377        chart.FixedYAxisMin = axisMin;
378        chart.FixedYAxisMax = axisMax;
379      }
380    }
381
382    // reorder chart controls so that they always appear in the same order as in the list view
383    // the table layout containing the controls should be suspended before calling this method
384    private void ReOrderControls() {
385      var tl = partialDependencePlotTableLayout;
386      tl.Controls.Clear();
387      int row = 0, column = 0;
388      double yValue = Content.Model.GetEstimatedValues(sharedFixedVariables, new[] { 0 }).Single();
389      string title = Content.ProblemData.TargetVariable + ": " + yValue.ToString("G5", CultureInfo.CurrentCulture);
390
391      foreach (var v in VisibleVariables) {
392        var chartsPanel = groupingPanels[v];
393        tl.Controls.Add(chartsPanel, column, row);
394
395        var chart = partialDependencePlots[v];
396        chart.YAxisTitle = column == 0 ? title : string.Empty;
397        column++;
398
399        if (column == MaxColumns) {
400          row++;
401          column = 0;
402        }
403      }
404    }
405
406    private void SetStyles() {
407      var tl = partialDependencePlotTableLayout;
408      tl.RowStyles.Clear();
409      tl.ColumnStyles.Clear();
410      int numVariables = VisibleVariables.Count();
411      if (numVariables == 0)
412        return;
413
414      // set column styles
415      tl.ColumnCount = Math.Min(numVariables, MaxColumns);
416      for (int c = 0; c < tl.ColumnCount; c++)
417        tl.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100.0f / tl.ColumnCount));
418
419      // set row styles
420      tl.RowCount = (int)Math.Ceiling((double)numVariables / tl.ColumnCount);
421      var columnWidth = tl.Width / tl.ColumnCount; // assume all columns have the same width
422      var rowHeight = (int)(0.8 * columnWidth);
423      for (int r = 0; r < tl.RowCount; r++)
424        tl.RowStyles.Add(new RowStyle(SizeType.Absolute, rowHeight));
425    }
426
427    private async void partialDependencePlot_SolutionAdded(object sender, EventArgs<IRegressionSolution> e) {
428      var solution = e.Value;
429      foreach (var chart in partialDependencePlots.Values) {
430        if (sender == chart) continue;
431        await chart.AddSolutionAsync(solution);
432      }
433    }
434
435    private async void partialDependencePlot_SolutionRemoved(object sender, EventArgs<IRegressionSolution> e) {
436      var solution = e.Value;
437      foreach (var chart in partialDependencePlots.Values) {
438        if (sender == chart) continue;
439        await chart.RemoveSolutionAsync(solution);
440      }
441    }
442
443    private async void variableListView_ItemChecked(object sender, ItemCheckedEventArgs e) {
444      var item = e.Item;
445      var variable = item.Text;
446      var plot = partialDependencePlots[variable];
447      var chartsPanel = groupingPanels[variable];
448      var tl = partialDependencePlotTableLayout;
449
450      tl.SuspendLayout();
451      if (item.Checked) {
452        tl.Controls.Add(chartsPanel);
453        await plot.RecalculateAsync(false, false);
454      } else {
455        tl.Controls.Remove(chartsPanel);
456      }
457
458      if (tl.Controls.Count > 0) {
459        SetupYAxis();
460        ReOrderControls();
461        SetStyles();
462      }
463      tl.ResumeLayout();
464      tl.Refresh();
465      densityComboBox_SelectedIndexChanged(this, EventArgs.Empty);
466    }
467
468    private void automaticYAxisCheckBox_CheckedChanged(object sender, EventArgs e) {
469      limitView.ReadOnly = automaticYAxisCheckBox.Checked;
470      SetupYAxis();
471      partialDependencePlotTableLayout.Refresh();
472      densityComboBox_SelectedIndexChanged(this, EventArgs.Empty); // necessary to realign the density plots
473    }
474
475    private void limit_ValueChanged(object sender, EventArgs e) {
476      if (automaticYAxisCheckBox.Checked)
477        return;
478      SetupYAxis();
479      partialDependencePlotTableLayout.Refresh();
480      densityComboBox_SelectedIndexChanged(this, EventArgs.Empty); // necessary to realign the density plots
481    }
482
483    private void densityComboBox_SelectedIndexChanged(object sender, EventArgs e) {
484      if (Content == null)
485        return;
486
487      int si = densityComboBox.SelectedIndex;
488      if (si == 0) {
489        foreach (var densityChart in densityCharts.Values)
490          densityChart.Visible = false;
491      } else {
492        var indices = GetDensityIndices(si).ToList();
493
494        foreach (var entry in densityCharts) {
495          var variableName = entry.Key;
496          var densityChart = entry.Value;
497          if (!VisibleVariables.Contains(variableName) || partialDependencePlots[variableName].IsZoomed)
498            continue;
499
500          UpdateDensityChart(densityChart, variableName, indices);
501        }
502      }
503    }
504    private IEnumerable<int> GetDensityIndices(int selectedIndex) {
505      var problemData = Content.ProblemData;
506      return
507        selectedIndex == 1 ? problemData.TrainingIndices :
508        selectedIndex == 2 ? problemData.TestIndices :
509        problemData.AllIndices;
510    }
511    private void UpdateDensityChart(DensityChart densityChart, string variable, IList<int> indices = null) {
512      if (densityComboBox.SelectedIndex == 0)
513        return;
514      if (indices == null) {
515        indices = GetDensityIndices(densityComboBox.SelectedIndex).ToList();
516      }
517      if (Content.ProblemData.Dataset.VariableHasType<double>(variable)) {
518        var data = Content.ProblemData.Dataset.GetDoubleValues(variable, indices).ToList();
519        var plot = partialDependencePlots[variable] as PartialDependencePlot;
520        if (plot != null) {
521          var min = plot.FixedXAxisMin;
522          var max = plot.FixedXAxisMax;
523          var buckets = plot.DrawingSteps;
524          if (min.HasValue && max.HasValue) {
525            densityChart.UpdateChart(data, min.Value, max.Value, buckets);
526            densityChart.Width = plot.Width;
527
528            var gcPlotPosition = plot.InnerPlotPosition;
529            densityChart.Left = (int)(gcPlotPosition.X / 100.0 * plot.Width);
530            densityChart.Width = (int)(gcPlotPosition.Width / 100.0 * plot.Width);
531
532            densityChart.Visible = true;
533          }
534          plot.UpdateTitlePosition();
535        }
536      } else if (Content.ProblemData.Dataset.VariableHasType<string>(variable)) {
537        var data = Content.ProblemData.Dataset.GetStringValues(variable).ToList();
538        var plot = partialDependencePlots[variable] as FactorPartialDependencePlot;
539        if (plot != null) {
540          densityChart.UpdateChart(data);
541          densityChart.Width = plot.Width;
542
543          var gcPlotPosition = plot.InnerPlotPosition;
544          densityChart.Left = (int)(gcPlotPosition.X / 100.0 * plot.Width);
545          densityChart.Width = (int)(gcPlotPosition.Width / 100.0 * plot.Width);
546
547          densityChart.Visible = true;
548
549          plot.UpdateTitlePosition();
550        }
551      }
552    }
553
554    private void columnsNumericUpDown_ValueChanged(object sender, EventArgs e) {
555      MaxColumns = (int)columnsNumericUpDown.Value;
556      int columns = Math.Min(VisibleVariables.Count(), MaxColumns);
557      if (columns > 0) {
558        var tl = partialDependencePlotTableLayout;
559        MaxColumns = columns;
560        tl.SuspendLayout();
561        ReOrderControls();
562        SetStyles();
563        tl.ResumeLayout();
564        tl.Refresh();
565        densityComboBox_SelectedIndexChanged(this, EventArgs.Empty);
566      }
567    }
568
569    private async void solution_ModelChanged(object sender, EventArgs e) {
570      foreach (var variable in VisibleVariables) {
571        var pdp = partialDependencePlots[variable];
572        var densityChart = densityCharts[variable];
573        // recalculate and refresh
574        await pdp.RecalculateAsync(false, false);
575        pdp.Refresh();
576        UpdateDensityChart(densityChart, variable);
577      }
578    }
579
580    // flag that the current change is not triggered by a manual change from within a single plot
581    private bool setVariableValues = false;
582    private void variableValuesComboBox_SelectedValueChanged(object sender, EventArgs e) {
583      if (variableValuesModeComboBox.SelectedIndex == -1)
584        return; // changed to "manual" due to manual change of a variable
585      setVariableValues = true;
586      UpdateVariableValues();
587      setVariableValues = false;
588    }
589    private void rowNrNumericUpDown_ValueChanged(object sender, EventArgs e) {
590      if ((string)variableValuesModeComboBox.SelectedItem != "Row") {
591        variableValuesModeComboBox.SelectedItem = "Row"; // triggers UpdateVariableValues
592      } else {
593        setVariableValues = true;
594        UpdateVariableValues();
595        setVariableValues = false;
596      }
597    }
598    private void UpdateVariableValues() {
599      string mode = (string)variableValuesModeComboBox.SelectedItem;
600
601      var dataset = Content.ProblemData.Dataset;
602      object[] newRow;
603
604      if (mode == "Row") {
605        int rowNumber = (int)rowNrNumericUpDown.Value;
606        newRow = sharedFixedVariables.VariableNames
607          .Select<string, object>(variableName => {
608            if (dataset.DoubleVariables.Contains(variableName)) {
609              return dataset.GetDoubleValue(variableName, rowNumber);
610            } else if (dataset.StringVariables.Contains(variableName)) {
611              return dataset.GetStringValue(variableName, rowNumber);
612            } else {
613              throw new NotSupportedException("Only double and string(factor) columns are currently supported.");
614            }
615          }).ToArray();
616      } else {
617        newRow = sharedFixedVariables.VariableNames
618          .Select<string, object>(variableName => {
619            if (dataset.DoubleVariables.Contains(variableName)) {
620              var values = dataset.GetDoubleValues(variableName);
621              return
622                mode == "Mean" ? values.Average() :
623                mode == "Median" ? values.Median() :
624                mode == "Most Common" ? MostCommon(values) :
625                throw new NotSupportedException();
626            } else if (dataset.StringVariables.Contains(variableName)) {
627              var values = dataset.GetStringValues(variableName);
628              return
629                mode == "Mean" ? MostCommon(values) :
630                mode == "Median" ? MostCommon(values) :
631                mode == "Most Common" ? MostCommon(values) :
632                throw new NotSupportedException();
633            } else {
634              throw new NotSupportedException("Only double and string(factor) columns are currently supported.");
635            }
636          }).ToArray();
637      }
638
639      sharedFixedVariables.ReplaceRow(0, newRow);
640    }
641
642    private static T MostCommon<T>(IEnumerable<T> values) {
643      return values.GroupBy(x => x).OrderByDescending(g => g.Count()).Select(g => g.Key).First();
644    }
645
646    // ToolTips cannot be shown longer than 5000ms, only by using ToolTip.Show manually
647    // See: https://stackoverflow.com/questions/8225807/c-sharp-tooltip-doesnt-display-long-enough
648    private void variableValuesModeComboBox_MouseHover(object sender, EventArgs e) {
649      string tooltipText = @"Sets each variable to a specific value:
650    Row - Selects the value based on a specified row of the dataset.
651    Mean - Sets the value to the arithmetic mean of the variable.
652    Median - Sets the value to the median of the variable.
653    Most Common - Sets the value to the most common value of the variable (first if multiple).
654
655Note: For categorical values, the most common value is used when selecting Mean, Median or Most Common.";
656      toolTip.Show(tooltipText, variableValuesModeComboBox, 30000);
657      toolTip.Active = true;
658    }
659    private void variableValuesModeComboBox_MouseLeave(object sender, EventArgs e) {
660      toolTip.Active = false;
661    }
662  }
663}
Note: See TracBrowser for help on using the repository browser.