www.gusucode.com > risk 工具箱matlab源码程序 > risk/+risk/+internal/+app/BinningApp.m
classdef (Sealed) BinningApp < toolpack.desktop.ToolGroup % BINNINGAPP is a class that provides binning functionalities credit scoring % % Syntax: % % h = BinningApp() % % Description: % % BINNINGAPP opens a Toolstrip App allowing the user to perform automatic % and manual binning of the data stored in the creditscorecard object, % sc. % (...) % % % Output Arguments: % % h - Handle to the Toolstrip object % % Copyright 2016 The MathWorks, Inc. properties (Access = protected) % Toolstrip properties CtrlsOverview % Stores toolstrip's UIControls for the "Binning" Tab CtrlsManual % Stores toolstrip's UIControls for the "Manual" Tab OverviewFigure % The main figure in the UI, that hosts all subplots TabGroup % the tab group in the toolstrip PredictorInfo % Figure to store the BININFO statistics BinInfo % Figure to store the table for binning information PlotSettings % Plot options used by PLOTBINS TableSettings % Table settings used by BININFO isPlotChanged % Boolean indicating whether algorithm or plot % settings were modified isWOEChanged = false; % Boolean for when the WOE checkbox is ticked isBinTextChanged = false; isTableViewChanged = false; MembersCheckbox = 'overview'; isViewExpanded % Boolean indicating whether or not the view is expanded isAlgoUICalled = false; % Boolean indicating if the BinningAppAlgorithmSettings class is used NumApps = 0; % Integer. If >0, the existing data must be deleted before loading new data isInitPlot = true; % Boolean. Used by the 'Plot Options' button callback isInitTable = true; % Boolean. Used by the 'Plot Options' button callback PlotMenu % Drop down menu on the 'Plot Options' button TableMenu % Drop down menu on the 'Table Options' button nCallBinText = false; % Boolean used during initialization of PlotMenu, with selected items nCallWOE = false; % Boolean used during initialization of PlotMenu, with selected WOE nCallStats = 0; % Plot properties Legend TooltipOn % Boolean indicating whether or not the tooltip is displayed on the histograms TooltipHandleAxes TooltipHandleBins SelectedAxes = {}; AllAxesHandles = struct(); BinTextHandles = struct(); PlotPanel % UIpanel to store all the subplots ScrollBar % Track the scroll bar position to slide the main figure's uipanel ZoomedFigureAxesPosition % Manual binning properties ManualBinningPanel % TSPanel that in the Manual Binning section that updates with the predictor data type. ManualBinningSection % ToolSection to store the Manual binning section ZoomedFigure % Store the expanded view when manual binning is performed ZoomedAxes % Store the axes in the expanded view BarHandles % Store the handle to the bar plots LeftEdgesXData % Array that stores the X-position of the left edges of the histogram RightEdgesXData % Array that stores the X-position of the right edges of the histogram LeftEdgeValue % Value of LeftEdges for the selected bin RightEdgeValue % Value of RightEdges for the selected bin BinHeights % Height, in bin count, of each bin NumBins % Number of bins in the histogram of the zoomed figure SelectedBins % Array storing the bin numbers of selected bins BinMembers % object of type TSList that displays the members of the selected bin (categorical) isMergeApplied = false; isShiftApplied = false; isSplitApplied = false; isDuplicateCutPoint = false; isPredictorChanged = false; % Split / Merge properties SplitFigure OldNumBins = 2; MaxNumBinsSplit = 4; % For a maximum of 5 sub-bins SplitBinLine % Array of line objects that are used as visual cue for splitting bins (Numeric) CutPointBox % Array that holds the cutpoint as a text value InvalidCutPoint = false; % If the user clicks ok before validating the edit box cutpoint SplitCutPoints % Store the values of the new cutpoints for the selected bin to split OldCutPoints % Store the cutpoints before confirming the split NumMergedBins = 0; % Used when merging happens, to determine which bin to select. Positive: merge to the right. Negative: merge to the left. SplitCategories % Nx2 cell array. Stores the values of the new catgroupings for the selected bin to split OldCatGroupings % Store the catgroupings before confirming the split SelectedCategories % Cell array to store selected categories used for bin split. First element is the bin number DefaultBinListing % Cell array of original bins and their numbers OldBinNumber = []; % Used by the WindowButtonMotionFcn for the zoomed figure PatchColor = [0 153/255 1]; % Status bar Java objects JStatusBar JProgressBar JLabel JFrame end properties (Access = protected) % Credit Scorecard properties OriginalData Scorecard = []; OldScorecard = []; PredictorVars PreviousPredictor SelectedPredictors ModifiedPredictors isScorecardExported = false; StatsTable = struct(); ResponseChangedEvent MonotoneRadioBtn NoBinningRadioBtn % Algorithm settings Algorithm = struct('AlgoName',''); DefaultAlgoSettings = struct(); % Bin info statistics BinInfoStats = {'odds','woe','infovalue','entropy','members'}; end properties(Access = private) ActionListeners end events PlotSettingsChanged SelectedFigureTab end methods (Access = public) %% Constructor function obj = BinningApp() % Get the Binning App handle from the root obj = obj@toolpack.desktop.ToolGroup('binningexplorer',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OverviewFigureName')); % Create the app obj.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:AppDescription'); % Setup toolstrip sections tab1 = obj.addTab('overview',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OverviewTabName')); obj.SelectedTab = 'overview'; obj.addSection(tab1); % Disable all controls that are not I/O related. This will % promt the user to first load a creditscorecard object. obj.enableControls(false); % Set help for the Binning Explorer obj.setContextualHelpCallback(@(src,ev) obj.launchBinningExplorerHelp) % Hide the View tab g = obj.Peer.getWrappedComponent; g.putGroupProperty(com.mathworks.widgets.desk.DTGroupProperty.ACCEPT_DEFAULT_VIEW_TAB, false); % Customize the App's icon Icon = getIcon('Binning_App_Icon_16.png'); g.putGroupProperty(com.mathworks.widgets.desk.DTGroupProperty.ICON,Icon.Peer); % Open the app obj.open; obj.setClosingApprovalNeeded(true); obj.ActionListeners.GroupAction = addlistener(obj,'GroupAction',@obj.CloseReqFcn); % Hide Data Browser md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.hideClient('DataBrowserContainer',obj.Name) % Setup the initial window size Width = 980; Height = 1000; frame = md.getFrameContainingGroup(obj.Name); frame.setSize(Width,Height); obj.ActionListeners.PlotSettings = addlistener(obj,'PlotSettingsChanged',@obj.applyPlotSettings); obj.ActionListeners.SelectedFigure = addlistener(obj,'SelectedFigureTab',@obj.updateZoomedFigure); end %% Doc center page for BinningExplorer function launchBinningExplorerHelp(~) helpview(fullfile(docroot,'risk','risk.map'),'BinningExplorerDoc'); end %% Get/Set methods function Predictors = getSelectedPredictors(obj) % Called by the BinningAlgorithmSettings method to retrieve the % SelectedPredictors property of the app. Predictors = obj.SelectedPredictors; end function Predictors = getPredictors(obj) Predictors = obj.PredictorVars; end function NumPredictors = getNumericPredictors(obj) NumPredictors = obj.Scorecard.NumericPredictors(:); end function CatPredictors = getCategoricalPredictors(obj) CatPredictors = obj.Scorecard.CategoricalPredictors(:); end function OrdPredictors = getOrdinalPredictors(obj) CatPredictors = obj.getCategoricalPredictors(); NumPreds = length(CatPredictors); isOrdinalPreds = false(NumPreds,1); for i = 1 : NumPreds data = obj.Scorecard.Data; if ~iscell(data.(CatPredictors{i})) && isordinal(data.(CatPredictors{i})) isOrdinalPreds(i) = true; else isOrdinalPreds(i) = false; end end OrdPredictors = CatPredictors(isOrdinalPreds); end function plotSettings = getPlotSettings(obj) Values = struct2cell(obj.PlotSettings); Fields = fieldnames(obj.PlotSettings); plotSettings = [Fields(1) Values(1) Fields(2) Values(2)]; end %% Toolstrip Sections and callbacks function obj = addSection(obj,Tab) % Add section to the selected tab 'Tab' TabName = Tab.Name; % File section if strcmpi(TabName,'overview') sec = toolpack.desktop.ToolSection('data',risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataSection')); sec.add(obj.LocalFileSection); Tab.add(sec); end % Predictor section sec = toolpack.desktop.ToolSection('predictor',risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorSection')); sec.add(obj.LocalPredictorSection(TabName)); Tab.add(sec); % Algorithm / Automatic binning section sec = toolpack.desktop.ToolSection('algo',risk.internal.app.utils.getMessage('risk:binningexplorerapp:AutoBinningSection')); sec.add(obj.LocalAlgoSection(TabName)); Tab.add(sec); % Manual binning section if strcmpi(Tab.Name,'overview') sec = toolpack.desktop.ToolSection('binning',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinningSection')); sec.add(obj.LocalBinningSection); Tab.add(sec); end % Split / Merge section if strcmpi(Tab.Name,'manual') sec = toolpack.desktop.ToolSection('splitmerge',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinningSection')); sec.add(obj.LocalSplitMergeSection); Tab.add(sec); obj.ManualBinningSection = sec; end % Plot section sec = toolpack.desktop.ToolSection('plot',risk.internal.app.utils.getMessage('risk:binningexplorerapp:PlotSection')); sec.add(obj.LocalPlotSection(TabName)); Tab.add(sec); % Table section sec = toolpack.desktop.ToolSection('table',risk.internal.app.utils.getMessage('risk:binningexplorerapp:TableSection')); sec.add(obj.LocalTableSection(TabName)); Tab.add(sec); % Export section if strcmpi(Tab.Name,'overview') sec = toolpack.desktop.ToolSection('export',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportSection')); sec.add(obj.LocalExportSection); Tab.add(sec); end end %% File and Update Section function section = LocalFileSection(obj) importDataBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportData'), ... toolpack.component.Icon.IMPORT_24); % importDataBtn.setAutoMnemonicEnabled(true); importDataBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL; importDataBtn.Name = 'importbutton'; importDataBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportDataTooltip'); section = toolpack.component.TSPanel('f:p:g','f:p,1dlu'); section.Name = 'FileSection'; section.add(importDataBtn,'xy(1,1,''c,c'')'); obj.ActionListeners.Load = addlistener(importDataBtn,'ActionPerformed',@obj.loadData); obj.CtrlsOverview.FileButtons = importDataBtn; end function section = LocalExportSection(obj) exportScorecardBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Export'), ... toolpack.component.Icon.CONFIRM_24); exportScorecardBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL; exportScorecardBtn.Name = 'exportbutton'; exportScorecardBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportTooltip'); section = toolpack.component.TSPanel('f:p:g','f:p,1dlu'); section.Name = 'ExportSection'; section.add(exportScorecardBtn,'xy(1,1,''c,c'')'); obj.ActionListeners.Export = addlistener(exportScorecardBtn,'ActionPerformed',@obj.exportScorecard); obj.CtrlsOverview.UpdateButtons = exportScorecardBtn; end %% Predictor Section function section = LocalPredictorSection(obj,TabName) if strcmpi(TabName,'manual') Name1 = obj.CtrlsOverview.PredictorButtons(2).Text; else Name1 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BlankSelect'); end if isempty(obj.PredictorVars) Name2 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BlankSelect'); else Name2 = obj.SelectedPredictors{1}; end PredLabel = toolpack.component.TSLabel(risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorType')); PredTypeBtn = toolpack.component.TSDropDownButton(Name1); PredTypeBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorTypeTooltip'); PredTypeBtn.Popup = obj.setPredTypeList('text_only'); PredTypeBtn.Name = 'predictortypebutton'; addlistener(PredTypeBtn.Popup,'ListItemSelected',... @(src,ev)obj.setPredTypeString(src,ev,TabName)); PredListLabel = toolpack.component.TSLabel(risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectedPredictor')); SelectedPredBtn = toolpack.component.TSDropDownButton(Name2); SelectedPredBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectedPredictorTooltip'); SelectedPredBtn.Name = 'selectedpredictorsbutton'; SelectedPredBtn.Popup = obj.setPredictorList('text_only'); if ~isempty(obj.PredictorVars) addlistener(SelectedPredBtn.Popup,'ListItemSelected',... @(src,ev)obj.selectAxesWrapper(src,ev,TabName)); end section = toolpack.component.TSPanel('l:p,4dlu,l:p','f:p:g,c:p,1dlu,c:p,f:p:g'); section.Name = 'Predictor'; section.add(PredListLabel,'xy(1,2)'); section.add(SelectedPredBtn,'xy(1,4)'); section.add(PredLabel,'xy(3,2)'); section.add(PredTypeBtn,'xy(3,4)'); if strcmpi(TabName,'overview') obj.CtrlsOverview.PredictorButtons = [PredLabel ... PredTypeBtn PredListLabel SelectedPredBtn]; else obj.CtrlsManual.PredictorButtons = [PredLabel ... PredTypeBtn PredListLabel SelectedPredBtn]; end end function popup = setPredTypeList(~,TextOption) listItems = struct( ... 'Title', '', ... 'Style', '', ... 'Header', cell(3,1)); % Numeric listItems(1).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); listItems(1).Style = TextOption; listItems(1).Header = false; % Categorical listItems(2).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); listItems(2).Style = TextOption; listItems(2).Header = false; % Ordinal listItems(3).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal'); listItems(3).Style = TextOption; listItems(3).Header = false; popup = toolpack.component.TSDropDownPopup(listItems,TextOption); popup.Name = 'predictortypelistpopup'; end function obj = setPredTypeString(obj,varargin) % Callback on the popup menu from the 'Predictor type' button. % It updates the string that appears on the button src = varargin{1}; TabName = varargin{3}; % Get the control button if strcmpi(TabName,'overview') PredTypeBtn = obj.CtrlsOverview.PredictorButtons(2); if obj.isViewExpanded AltTabBtn = obj.CtrlsManual.PredictorButtons(2); else AltTabBtn = []; end else PredTypeBtn = obj.CtrlsManual.PredictorButtons(2); AltTabBtn = obj.CtrlsOverview.PredictorButtons(2); end idx = src.SelectedIndex; PredType = src.Items(idx).Title; PredTypeBtn.Text = PredType; % Update the other tab's button if ~isempty(AltTabBtn) AltTabBtn.Text = PredType; end % Make the predictor type conversion and update the plot obj.setPredictorType(PredTypeBtn); end function obj = setPredictorType(obj,varargin) % Callback on the 'Predictor Type' button. It sets predictor % types and updates the scorecard object src = varargin{1}; sc = obj.Scorecard; NumPreds = length(obj.SelectedPredictors); if strcmpi(src.Text,'ordinal') PredictorType = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); isOrdinal = true; else PredictorType = src.Text; isOrdinal = false; end % Warn if the user converts from Numeric to Categorical, since % converting back to Numeric could result in loss of the % previous (numeric) binning. if any(ismember(obj.SelectedPredictors,obj.getNumericPredictors())) ... && strcmpi(PredictorType,'categorical') Choice = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataTypeConversionDlg',lower(src.Text)),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataTypeConversionTitle'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:No')); switch lower(Choice) case 'no' src.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); return; end end for i = 1 : NumPreds Predictor = obj.SelectedPredictors{i}; sc = modifypredictor(sc,Predictor,'PredictorType',... PredictorType,'Ordinal',isOrdinal); obj.Scorecard = sc; end obj.ModifiedPredictors = obj.SelectedPredictors; obj.isScorecardExported = false; % Update the status of the "Members" checkbox if strcmpi(PredictorType,'categorical') PreviousCheckedState = obj.CtrlsOverview.TableButtons(end).Selected ; obj.CtrlsOverview.TableButtons(end).Enabled = true; obj.CtrlsOverview.TableButtons(end).Selected = PreviousCheckedState; else obj.CtrlsOverview.TableButtons(end).Enabled = false; end % Update the relevant plots obj.isPlotChanged = true; obj.isPredictorChanged = true; obj.showPlots('predictortype'); obj.isPredictorChanged = false; obj.isPlotChanged = false; end function popup = setPredictorList(obj,TextOption) % Callback on the "Selected Predictors" drop-down list. It % lists the predictors loaded in the app. if isempty(obj.PredictorVars) listItems = struct('Title','','Style','text_only','Header',true); else listItems = struct( ... 'Title', '', ... 'Style', TextOption, ... 'Header', cell(length(obj.PredictorVars),1)); Predictors = sort(obj.PredictorVars); for i = 1 : length(Predictors) listItems(i).Title = Predictors{i}; listItems(i).Header = false; end end popup = toolpack.component.TSDropDownPopup(listItems,TextOption); popup.Name = 'predictorlistpopup'; end function obj = selectAxesWrapper(obj,varargin) % Callback on the "Selected Predictor" drop-down list. src = varargin{1}; if nargin == 4 TabName = varargin{3}; else TabName = ''; end % First update the string on the button if any(strcmpi(TabName,{'','overview'})) SelectedPredBtn = obj.CtrlsOverview.PredictorButtons(4); if obj.isViewExpanded AltTabBtn = obj.CtrlsManual.PredictorButtons(4); else AltTabBtn = []; end else SelectedPredBtn = obj.CtrlsManual.PredictorButtons(4); AltTabBtn = obj.CtrlsOverview.PredictorButtons(4); end idx = src.SelectedIndex; Predictor = src.Items(idx).Title; SelectedPredBtn.Text = Predictor; % Update the other tab's button if ~isempty(AltTabBtn) AltTabBtn.Text = Predictor; end % Select the corresponding axes hax = obj.AllAxesHandles.(src.Items(src.SelectedIndex).Title); obj.isPredictorChanged = true; obj.selectAxes(hax(1),[]); obj.isPredictorChanged = false; % Shift the slider % Use obj.SelectedAxes if the manual figure is open hax = obj.SelectedAxes(1); setValue(obj.ScrollBar,100*(1-hax.Position(2)-hax.Position(4))); end %% Algorithm / Automatic Binning Section function section = LocalAlgoSection(obj,TabName) algoBtn = toolpack.component.TSSplitButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',... obj.Algorithm.AlgoName),toolpack.component.Icon.RUN_24); algoBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL; algoBtn.Popup = obj.setAlgoList('icon_text_description'); algoBtn.Name = 'algobutton'; algoBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgoTooltip'); obj.ActionListeners.RunAlgo = addlistener(algoBtn,'ActionPerformed',@obj.runAlgorithm); obj.ActionListeners.SetAlgo = addlistener(algoBtn.Popup,'ListItemSelected',... @(src,ev)obj.setAlgoName(src,ev,TabName)); algoSettings = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:AlgoOptions'),... toolpack.component.Icon.SETTINGS_24); algoSettings.Orientation = toolpack.component.ButtonOrientation.VERTICAL; algoSettings.Name = 'algooptionsbutton'; algoSettings.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:AlgoOptionsTooltip'); obj.ActionListeners.SetAlgoUI = addlistener(algoSettings,... 'ActionPerformed',@(src,ev)obj.setAlgoSettingsUI(src,ev,TabName)); section = toolpack.component.TSPanel('110px,10px,f:p:g','f:p,1dlu'); section.Name = 'AlgoSection'; section.add(algoBtn,'xy(1,1)'); section.add(algoSettings,'xy(3,1)'); if strcmpi(TabName,'overview') obj.CtrlsOverview.AlgoButtons = [algoBtn;algoSettings]; else obj.CtrlsManual.AlgoButtons = [algoBtn;algoSettings]; end end function popup = setAlgoList(~,TextOption) listItems = struct( ... 'Title', '', ... 'Description', '', ... 'Style', '', ... 'Icon', [], ... 'Header', cell(3,1)); % Monotone listItems(1).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone'); listItems(1).Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MonotoneDescription'); listItems(1).Style = TextOption; listItems(1).Icon = getIcon('Monotone_24.png'); listItems(1).Header = false; % Equal Frequency listItems(2).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EF'); listItems(2).Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EFDescription'); listItems(2).Style = TextOption; listItems(2).Icon = getIcon('Equal_Frequency_24.png'); listItems(2).Header = false; % Equal Width listItems(3).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EW'); listItems(3).Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:EWDescription'); listItems(3).Style = TextOption; listItems(3).Icon = getIcon('Equal_Width_24.png'); listItems(3).Header = false; popup = toolpack.component.TSDropDownPopup(listItems,TextOption); popup.Name = 'algolistpopup'; end function obj = setAlgoName(obj,varargin) S = dbstack; if strcmpi(S(2).file(1:end-2),'BinningAppAlgorithmSettings') AlgoName = varargin{1}; TabName = varargin{2}; obj.isAlgoUICalled = varargin{3}; AlgoNameDisp = risk.internal.app.utils.formatNamesWithoutSpace({AlgoName},'equal'); AlgoNameDisp = AlgoNameDisp{:}; else src = varargin{1}; idx = src.SelectedIndex; TabName = varargin{3}; AlgoNameDisp = src.Items(idx).Title; AlgoName = regexprep(AlgoNameDisp,' ',''); end obj.Algorithm.AlgoName = AlgoName; % The algorithm settings are default unless called by the % settings UI if ~obj.isAlgoUICalled obj.Algorithm.Settings = obj.DefaultAlgoSettings.(AlgoName); end % Update the name of the algorithm on the button if strcmpi(TabName,'overview') AlgoBtn = obj.CtrlsOverview.AlgoButtons(1); if obj.isViewExpanded AltTabBtn = obj.CtrlsManual.AlgoButtons(1); else AltTabBtn = []; end else AlgoBtn = obj.CtrlsManual.AlgoButtons(1); AltTabBtn = obj.CtrlsOverview.AlgoButtons(1); end AlgoBtn.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',AlgoNameDisp); if ~isempty(AltTabBtn) AltTabBtn.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',AlgoNameDisp); end obj.runAlgorithm; % Reset the flag for obj.isAlgoUICalled obj.isAlgoUICalled = false; end function runAlgorithm(obj,varargin) % This callback is a wrapper around the method 'obj.showPlots' obj.isPlotChanged = true; obj.showPlots(); obj.isPlotChanged = false; end function setAlgoSettingsUI(obj,varargin) % This method opens a UI to input the binning options. % Create a handle class that opens a UI, where the user inputs % the binning options. The options are stores in a property % 'Settings' and retried to be stored in obj. TabName = varargin{3}; ba = risk.internal.app.BinningAppAlgorithmSettings(obj,... obj.Algorithm.AlgoName,TabName); hf = ba.getFigure(); uiwait(hf); end function obj = setAlgoSettings(obj,Settings) % Settings is a structure containing the algorithm settings, % for the given algorithm name set by 'setAlgoName'. obj.Algorithm.AlgoName = Settings.AlgoName; obj.Algorithm.Settings = Settings.Settings; end function Settings = getAlgoSettings(obj) Settings.AlgoName = obj.Algorithm.AlgoName; Settings.Settings = obj.Algorithm.Settings; end function obj = setDefaultAlgoSettings(obj) obj.DefaultAlgoSettings.Monotone = {'InitialNumBins',10,... 'Trend','Auto','SortCategories','Odds'}; obj.DefaultAlgoSettings.EqualFrequency = {'NumBins',5,... 'SortCategories','Odds'}; obj.DefaultAlgoSettings.EqualWidth = {'NumBins',5,... 'SortCategories','Odds'}; end function Settings = getDefaultAlgoSettings(obj,algoName) % Called by BinningAppAlgorithmSettings UI Settings.AlgoName = algoName; Settings.Settings = obj.DefaultAlgoSettings.(algoName); end %% Manual Binning Section function section = LocalBinningSection(obj) % The description is 'expand' when in manual binning mode. It % opens a contextual tab with a new figure and new controls on % the toolstrip. The description becomes 'overview' when the % user closes the zoomed-in figure Icon = getIcon('Manual_Binning_24.png'); expandBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinning'),... Icon); expandBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL; expandBtn.Name = 'manualbinningbutton'; expandBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualBinningTooltip'); section = toolpack.component.TSPanel('f:p:g','f:p,1dlu'); section.Name = 'ViewSection'; section.add(expandBtn,'xy(1,1,''c,c'')'); obj.ActionListeners.ExpandView = addlistener(expandBtn,... 'ActionPerformed',@obj.expandView); obj.CtrlsOverview.ExpandButton = expandBtn; end function section = LocalSplitMergeSection(obj) % 1. Split button SplitIcon = getIcon('Split_Binning_24.png'); SplitBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Split'),SplitIcon); SplitBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL; SplitBtn.Name = 'splitbutton'; SplitBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:SplitTooltip'); % 2. Merge button MergeIcon = getIcon('Merge_Binning_24.png'); MergeBtn = toolpack.component.TSButton(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Merge'),MergeIcon); MergeBtn.Orientation = toolpack.component.ButtonOrientation.VERTICAL; MergeBtn.Enabled = false; MergeBtn.Name = 'mergebutton'; MergeBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MergeTooltip'); % 3. Labels for bin members or edges Predictor = obj.SelectedPredictors{1}; if ismember(Predictor,obj.getNumericPredictors()) String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Edges'); Edge1 = toolpack.component.TSTextField(num2str(obj.LeftEdgeValue),4); Edge2 = toolpack.component.TSTextField(num2str(obj.RightEdgeValue),4); Edge1.Name = 'leftedge'; Edge2.Name = 'rightedge'; Edge1.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Edge1Tooltip'); Edge2.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Edge2Tooltip'); obj.CtrlsManual.LeftEdge = Edge1; obj.CtrlsManual.RightEdge = Edge2; obj.ActionListeners.ShiftCutPoint(1) = ... addlistener(obj.CtrlsManual.LeftEdge,'ActionPerformed',@obj.shiftCutpoint); obj.ActionListeners.ShiftCutPoint(2) = ... addlistener(obj.CtrlsManual.RightEdge,'ActionPerformed',@obj.shiftCutpoint); else String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Members'); Members = toolpack.component.TSLabel(obj.formatBinMembersText()); Members.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MembersTooltip'); obj.CtrlsManual.Members = Members; if length(obj.BinMembers) < 2 SplitBtn.Enabled = false; end Members.Name = 'binmemberslist'; end Label = toolpack.component.TSLabel(String); section = toolpack.component.TSPanel(... 'c:p,3dlu,c:p,3dlu,f:p,1dlu,f:p,3dlu,f:p',... 'f:p:g,c:p:g,f:p:g,c:p'); section.Name = 'SplitMergeSection'; section.add(SplitBtn,'xywh(1,1,1,4)'); section.add(MergeBtn,'xywh(3,1,1,4)'); if ismember(Predictor,obj.getNumericPredictors()) section.add(Label,'xy(5,2)'); section.add(Edge1,'xy(7,2)'); section.add(Edge2,'xy(9,2)'); else section.add(Label,'xy(5,1)'); section.add(Members,'xywh(5,2,1,1)'); end obj.CtrlsManual.ManualButtons = [SplitBtn;MergeBtn;Label]; obj.ActionListeners.SplitBin = addlistener(SplitBtn,'ActionPerformed',@obj.splitBin); obj.ActionListeners.MergeBin = addlistener(MergeBtn,'ActionPerformed',@obj.mergeBins); obj.ManualBinningPanel = section; end function obj = expandView(obj,varargin) % Callback on the button in the 'Manual Binning' section. % Creates new tabbed figure document, associated with a % contextual tab for manual binning. % Set the main figure as the current figure to ensure that the % addition of the zoomed figure is tiled with the overview figure % Make sure the visibility is on set(groot,'DefaultFigureVisible','On') figure(obj.OverviewFigure); Predictor = obj.SelectedPredictors{1}; try if ~isempty(obj.TabGroup) if strcmpi(obj.ZoomedFigure.Tag,Predictor) figure(obj.ZoomedFigure); obj.isViewExpanded = true; else obj.updateZoomedFigure(); obj.isViewExpanded = true; end else createContextualTabAndFigure() end catch MException if strcmpi(MException.identifier,'MATLAB:class:InvalidHandle') createContextualTabAndFigure() else throw(MException) end end function createContextualTabAndFigure() hf = plotbins(obj.Scorecard,Predictor,obj.getPlotSettings{:}); hf.MenuBar = 'None'; hf.IntegerHandle = 'Off'; hf.HandleVisibility = 'Off'; hf.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualFigureName',... obj.SelectedPredictors{1}); hf.Tag = Predictor; hf.CloseRequestFcn = @obj.closeZoomedFigure; hf.WindowButtonDownFcn = @obj.selectBin; hf.KeyPressFcn = @obj.figureKeyPress; hf.KeyReleaseFcn = @obj.figureKeyRelease; hf.WindowKeyPressFcn = @obj.figureShiftPress; hf.WindowKeyReleaseFcn = @obj.figureShiftRelease; hf.WindowButtonMotionFcn = @obj.showBinInformation; hl = risk.internal.app.utils.extractChildren(hf,'legend'); hl.PickableParts = 'None'; hf.SizeChangedFcn = @obj.figureResizeCallback; % Highlight first bin hax = risk.internal.app.utils.extractChildren(hf,'axes'); if find(strcmpi(get(hax,'Tag'),'WOE')) == 1 hax = flipud(hax); end obj.SelectedBins = 1; obj.resetBinEdgesMembersAfterReplot(hax); obj.ZoomedFigure = hf; obj.createContextualTab(); obj.isViewExpanded = true; end end function obj = createContextualTab(obj,varargin) % Creates a contextual tab for manual binning. Called by % 'obj.expandView' obj.addFigure(obj.ZoomedFigure); if isempty(obj.TabGroup) tg = toolpack.desktop.TabGroup; tab = toolpack.desktop.ToolTab('manual',risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualTabName')); tg.Tabs = tab; tg.SelectedTab = tab; obj.addSection(tab); obj.addClientTabGroup(obj.ZoomedFigure,tg); obj.TabGroup = tg; end end function obj = selectBin(obj,varargin) % Highlights the bin that the user clicks on. This is a callback % (ButtonDownFcn) on the axes 'obj.ZoomedAxes'. Called by % 'obj.expandView' and 'obj.updateZoomedFigure' hax = obj.ZoomedAxes; if isempty(obj.ZoomedFigure.UserData) || ... any(strcmpi(obj.ZoomedFigure.UserData,{'ctrlclickoff','shiftclickoff'})) delete(risk.internal.app.utils.extractChildren(hax,'rectangle')); obj.resetBinControls(); end % Make sure the edit box is enabled obj.CtrlsManual.LeftEdge.Enabled = true; obj.CtrlsManual.RightEdge.Enabled = true; XPos = hax.CurrentPoint(1,1); YPos = hax.CurrentPoint(1,2); BinNumber = find(XPos >= obj.LeftEdgesXData & XPos < obj.RightEdgesXData); if ~isempty(BinNumber) YBins = sum(cell2mat({obj.BarHandles.YData}')); if YPos <= YBins(BinNumber) x = obj.LeftEdgesXData(BinNumber); y = 0; w = obj.BarHandles(1).BarWidth; h = YBins(BinNumber); rectangle('Parent',hax,'Position',[x y w h],... 'LineWidth',2,'EdgeColor','blue','Selected','On'); if ~isempty(obj.ZoomedFigure.UserData) && ... any(strcmpi(obj.ZoomedFigure.UserData,... {'ctrlclickon','shiftclickon'}))%obj.ZoomedFigure.UserData obj.SelectedBins = unique([obj.SelectedBins BinNumber]); % Disable editing bin edges for shifting obj.CtrlsManual.LeftEdge.Enabled = false; obj.CtrlsManual.RightEdge.Enabled = false; else obj.SelectedBins = BinNumber; end % Display the bin edges on the toolstrip Predictor = obj.SelectedPredictors{1}; if ismember(Predictor,obj.getNumericPredictors()) obj.setBinEdges(obj.SelectedBins); else obj.setBinMembers(obj.SelectedBins); end % If non-adjacent bins are selected, select the ones in % between for numeric predictors and update split / % merge controls if length(obj.SelectedBins) > 1 obj.CtrlsManual.ManualButtons(1).Enabled = false; obj.CtrlsManual.ManualButtons(2).Enabled = true; Clause1 = ismember(Predictor,obj.getNumericPredictors()); Clause2 = ~isempty(obj.ZoomedFigure.UserData) && ... any(strcmpi(obj.ZoomedFigure.UserData,... {'ctrlclickon','shiftclickon'})); Clause3 = ismember(Predictor,obj.getCategoricalPredictors()); Clause4 = ~isempty(obj.ZoomedFigure.UserData) && ... strcmpi(obj.ZoomedFigure.UserData,'shiftclickon'); if any(diff(obj.SelectedBins)>1) && ... ((Clause1 && Clause2) || (Clause3 && Clause4)) obj.selectIntermediaryBins(YBins); end else if ismember(Predictor,obj.getNumericPredictors())... || (ismember(Predictor,obj.getCategoricalPredictors()) ... && length(obj.BinMembers) > 1) obj.CtrlsManual.ManualButtons(1).Enabled = true; else obj.CtrlsManual.ManualButtons(1).Enabled = false; end end obj.CtrlsManual.ManualButtons(3).Enabled = true; else obj.resetBinControls(); end else obj.resetBinControls(); end end function obj = resetBinControls(obj) % If the user clicks outside the bins or a bin is selected for % the first time. Called by 'obj.selectBin'. obj.SelectedBins = []; obj.CtrlsManual.ManualButtons(1).Enabled = false; obj.CtrlsManual.ManualButtons(2).Enabled = false; obj.CtrlsManual.ManualButtons(3).Enabled = false; if ismember(obj.SelectedPredictors{1},obj.getNumericPredictors()) obj.CtrlsManual.LeftEdge.Text = ''; obj.CtrlsManual.RightEdge.Text = ''; obj.CtrlsManual.LeftEdge.Enabled = false; obj.CtrlsManual.RightEdge.Enabled = false; else obj.CtrlsManual.Members.Text = ''; end end function obj = highlightBin(obj,hax,BinNumber) % Called by 'obj.updateZoomedFigure'. Highlights the bin number % 'BinNumber' for manual binning hBar = risk.internal.app.utils.extractChildren(hax,'bar'); YDataBad = hBar(1).YData; YDataGood = hBar(2).YData; x = BinNumber - 0.5*hBar(1).BarWidth; y = 0; w = hBar(1).BarWidth; h = YDataBad(BinNumber) + YDataGood(BinNumber); rectangle('Parent',hax(1),'Position',[x y w h],'Linewidth',2,... 'EdgeColor','blue','Selected','On'); end function obj = selectIntermediaryBins(obj,YBins) % This method selects the intermediary bins when the user, say % selects bins 1 and 3. Called by 'obj.selectBin'. iMin = obj.SelectedBins(1); iMax = obj.SelectedBins(end); iMissingBins = setdiff(iMin:iMax,obj.SelectedBins); for i = 1 : length(iMissingBins) BinNumber = iMissingBins(i); x = obj.LeftEdgesXData(BinNumber); y = 0; w = obj.BarHandles(1).BarWidth; h = YBins(BinNumber); rectangle('Parent',obj.ZoomedAxes,'Position',... [x y w h],'LineWidth',2,'EdgeColor','blue',... 'Selected','On'); end obj.SelectedBins = iMin:iMax; end function obj = setBinEdgesAndNumber(obj,hax) % Defines the left and right edges of the bins in the bar plot, % as well as the total number of bins. Useful for methods that % need to locate the selected bin for splittinhg or merging. hBar = risk.internal.app.utils.extractChildren(hax,'bar'); if ~isempty(hBar) && all(isvalid(hBar)) obj.BarHandles = hBar; obj.NumBins = length(hBar(1).XData); obj.LeftEdgesXData = hBar(1).XData - 0.5*hBar(1).BarWidth; obj.RightEdgesXData = hBar(1).XData + 0.5*hBar(1).BarWidth; if length(obj.SelectedAxes) < 2 obj.BinHeights = sum(cell2mat({hBar.YData}'),1); end end end function obj = setBinEdges(obj,BinNumbers) % Called by 'obj.selectBin'. It displays the bin edges of the % selected bin, for numeric predictors, on the toolstrip. Predictor = obj.SelectedPredictors{1}; [~,cp,mv] = bininfo(obj.Scorecard,Predictor); % To avoid displaying "+/-Inf", we use the Min and/or Max % values if (BinNumbers(1) == 1) if ~obj.isShiftApplied || (obj.isShiftApplied && (obj.isDuplicateCutPoint || obj.isMergeApplied)) obj.LeftEdgeValue = mv(1); end if (BinNumbers(end) == length(cp)+1) if ~obj.isShiftApplied obj.RightEdgeValue = mv(2); end else obj.RightEdgeValue = cp(BinNumbers(end)); end elseif (BinNumbers(end) == length(cp)+1) obj.LeftEdgeValue = cp(BinNumbers(1)-1); if ~obj.isShiftApplied || (obj.isShiftApplied && obj.isDuplicateCutPoint) obj.RightEdgeValue = mv(2); end else obj.LeftEdgeValue = cp(BinNumbers(1)-1); obj.RightEdgeValue = cp(BinNumbers(end)); end % Update the bin edge display on the toolstrip obj.CtrlsManual.LeftEdge.Text = num2str(obj.LeftEdgeValue); obj.CtrlsManual.RightEdge.Text = num2str(obj.RightEdgeValue); end function obj = setBinMembers(obj,BinNumbers) % Called by 'obj.selectBin'. It displays the bin members of the % selected bin, for categorical predictors, on the toolstrip. [~,cg] = bininfo(obj.Scorecard,obj.SelectedPredictors{1}); Ind = zeros(length(cg.BinNumber),1); % If 'shiftclickon', then use all intermediary bins. Otherwise, % only the selected bins ('ctrlclickon') if ~isempty(obj.ZoomedFigure) && (isa(obj.ZoomedFigure,'handle') && isvalid(obj.ZoomedFigure))% First click on "Manual Binning" if strcmpi(obj.ZoomedFigure.UserData,'shiftclickon') Ind = BinNumbers(1):BinNumbers(end); else for i = 1 : length(BinNumbers) Ind = xor(Ind,(cg.BinNumber == BinNumbers(i))); end end else for i = 1 : length(BinNumbers) Ind = xor(Ind,(cg.BinNumber == BinNumbers(i))); end end obj.BinMembers = cg.Category(Ind); % Update the bin members display on the toolstrip obj.CtrlsManual.Members.Text = obj.formatBinMembersText(); % If the selected bin has only one member, disable the "Split" % button if length(obj.BinMembers) < 2 obj.CtrlsManual.ManualButtons(1).Enabled = false; end end function Text = formatBinMembersText(obj) % Called by obj.setBinMembers and LocalSplitMergeSection to % format the text label for the selected bin's members if length(obj.BinMembers) == 1 Text = obj.BinMembers{:}; elseif length(obj.BinMembers) == 2 Text = sprintf('<html>%s<br>%s</html>',obj.BinMembers{1},... obj.BinMembers{2}); else Text = sprintf('<html>%s<br>%s</html>',obj.BinMembers{1},... [obj.BinMembers{2} '...']); end end function obj = mergeBins(obj,varargin) % Callback on the Toolstrip button 'Merge'. It merges the bins % in 'obj.SelectedBins' into one. Bins must be adjacent. Predictor = obj.SelectedPredictors{1}; if ismember(Predictor,obj.getNumericPredictors()) [~,CutPoints] = bininfo(obj.Scorecard,Predictor); CutPoints(obj.SelectedBins(1:end-1)) = []; obj.Scorecard = modifybins(obj.Scorecard,Predictor,... 'CutPoints',CutPoints); else [~,CatGrouping] = bininfo(obj.Scorecard,Predictor); Ind = zeros(length(CatGrouping.BinNumber),1); for i = 1 : length(obj.SelectedBins) Ind = xor(Ind,(CatGrouping.BinNumber == obj.SelectedBins(i))); end CatGrouping.BinNumber(Ind) = obj.SelectedBins(1); if any(diff(CatGrouping.BinNumber) < 0) && ... ismember(Predictor,obj.getOrdinalPredictors()) w = warning('off','finance:internal:finance:binning:Categorical:NonMonotonicGrouping'); c = onCleanup(@() warning(w)); uiwait(msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalMergeNonAdjacentMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalMergeNonAdjacentTitle'),'warn','modal')); end obj.Scorecard = modifybins(obj.Scorecard,Predictor,... 'CatGrouping',CatGrouping); end obj.isScorecardExported = false; % Update the zoomed and overview figures obj.isMergeApplied = true; obj.isPlotChanged = true; notify(obj,'SelectedFigureTab'); % Update the Bin and Predictor Information tables obj.setBinInfo(); obj.setPredictorInfo(); % Disable the 'Merge' button and enable the 'Split button' obj.CtrlsManual.ManualButtons(2).Enabled = false; obj.isMergeApplied = false; % Bring the zoomed fgure to the foreground figure(obj.ZoomedFigure) end function obj = resetBinEdgesMembersAfterReplot(obj,hax) % Called by 'obj.updateZoomedFigure', 'obj.expandView' and % 'obj.mergeBins'. % Selects the first bin and set the bin edges of bin members % after the plot is refreshed. try if strcmpi(obj.SelectedPredictors{1},obj.PreviousPredictor) ||... obj.isMergeApplied || obj.isShiftApplied || obj.isSplitApplied if obj.NumMergedBins >= 0 BinNumber = obj.SelectedBins(1); else i1 = obj.SelectedBins(1); i2 = i1 + obj.NumMergedBins; if i2 <= 0 BinNumber = 1; else BinNumber = i2 + 1; end end else BinNumber = 1; end catch BinNumber = 1; end obj.SelectedBins = BinNumber; obj.highlightBin(hax,BinNumber); obj.ZoomedAxes = hax(1); obj.setBinEdgesAndNumber(obj.ZoomedAxes); if ismember(obj.SelectedPredictors{1},obj.getNumericPredictors()) obj.setBinEdges(BinNumber); else obj.setBinMembers(BinNumber); end end function obj = shiftCutpoint(obj,varargin) % Callback on the left and right edge text fields in the % contextual tab. It shifts the cutpoint when the user enters a % new valid value. src = varargin{1}; EdgeValue = str2double(src.Text); Predictor = obj.SelectedPredictors{1}; [~,cp,mv] = bininfo(obj.Scorecard,Predictor); [~,Stats] = predictorinfo(obj.Scorecard,Predictor); if strcmpi(src.Name,'leftedge') if (obj.SelectedBins == 1) Ind = 1; elseif (obj.SelectedBins == obj.NumBins) Ind = obj.SelectedBins; else Ind = obj.SelectedBins-1; end else Ind = obj.SelectedBins; end % Step 0: Troubleshoot edge cases if isnan(EdgeValue) msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsMsg1'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsTitle'),'warn','modal'); if strcmpi(src.Name,'leftedge') src.Text = num2str(obj.LeftEdgeValue); else src.Text = num2str(obj.RightEdgeValue); end return; elseif (strcmpi(src.Name,'leftedge') && EdgeValue>obj.RightEdgeValue)... || (strcmpi(src.Name,'rightedge') && EdgeValue<obj.LeftEdgeValue) msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsMsg2'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsTitle'),'warn','modal'); if strcmpi(src.Name,'leftedge') src.Text = num2str(obj.LeftEdgeValue); else src.Text = num2str(obj.RightEdgeValue); end return; end % Step 1: If more than one bin is selected, disable the text boxes if length(obj.SelectedBins) > 1 obj.CtrlsManual.LeftEdge.Enabled = false; obj.CtrlsManual.RightEdge.Enabled = false; return; end % Step 2: Shift the bin edge w = catchWarningAndBin(Ind,src); if ~isempty(w) c = onCleanup(@() warning(w)); end obj.isScorecardExported = false; obj.isShiftApplied = true; % Step 3: Update the zoomed and overview figures obj.isPlotChanged = true; notify(obj,'SelectedFigureTab'); obj.isPlotChanged = false; % Step 4: Update statistic information obj.setBinInfo(); obj.setPredictorInfo(); figure(obj.ZoomedFigure) obj.isShiftApplied = false; if obj.isMergeApplied obj.isMergeApplied = false; end obj.NumMergedBins = 0; function w = catchWarningAndBin(Ind,src) % Helper nested function. Returns the status of warnings % returned by the creditscorecard, displays the message % boxes and re-bins the data given the new rules. obj.OldCutPoints = cp; [w,okDuplicate] = catchWarningsAndUpdateCutPoints(); if (Ind == 1) || (Ind == obj.NumBins) if okDuplicate obj.isDuplicateCutPoint = true; end if strcmpi(src.Name,'leftedge') obj.LeftEdgeValue = EdgeValue; elseif (strcmpi(src.Name,'rightedge')) obj.RightEdgeValue = EdgeValue; end end function [w,ok1] = catchWarningsAndUpdateCutPoints() % Helper function that catches all warning due to % duplicate cutpoints, non-monotonicity of cutpoints, % and sets MinValue andMaxValue, when appropriate ok1 = false; if Ind == 1 Str = {'DuplicateCutPoints','MinAboveData',... 'MinAboveCutPoints','MinValue','CutPointsBelowMin'}; elseif Ind == obj.NumBins Str = {'DuplicateCutPoints','MaxBelowData',... 'MaxBelowCutPoints','MaxValue','CutPointsAboveMax'}; else Str = {'DuplicateCutPoints'}; end if (Ind == 1) || (Ind == obj.NumBins) [ok1,w1] = isWarningTriggered(Str{1}); [ok2,w2] = isWarningTriggered(Str{2}); [ok3,w3] = isWarningTriggered(Str{3}); ok = [ok1 ok2 ok3]; w = {w1 w2 w3}; if all(cellfun(@isempty,w)) w = []; else w = w{ok}; end if any(ok) MessageStr = cellfun(@(x) risk.internal.app.utils.getMessage(x),{w.identifier}','UniformOutput',false); uiwait(msgbox(MessageStr,... risk.internal.app.utils.getMessage(... ['risk:binningexplorerapp:' Str{4} 'TruncatedDataTitle']),... 'warn','modal')); end if ((Ind == 1) && strcmpi(src.Name,'leftedge')) || ... ((Ind == obj.NumBins) && strcmpi(src.Name,'rightedge')) obj.Scorecard = modifybins(obj.Scorecard,Predictor,Str{4},EdgeValue); elseif ((Ind == 1) && strcmpi(src.Name,'rightedge')) || ... ((Ind == obj.NumBins) && strcmpi(src.Name,'leftedge')) if Ind ==1 cp(Ind) = EdgeValue; else cp(Ind-1) = EdgeValue; end [ok4,w4] = isWarningTriggered(Str{5}); if ok4 MessageStr = cellfun(@(x) risk.internal.app.utils.getMessage(x),{w4.identifier}','UniformOutput',false); uiwait(msgbox(MessageStr,... risk.internal.app.utils.getMessage(... ['risk:binningexplorerapp:' Str{4} 'TruncatedDataTitle']),... 'warn','modal')); end [cp,isExit] = testForMonotonicity(cp); if isExit return; else obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CutPoints',cp); end w = {w1 w2 w3 w4}; if all(cellfun(@isempty,w)) w = []; else w = w{[ok ok4]}; end end else [ok,w] = isWarningTriggered(Str{1}); cp(Ind) = EdgeValue; if ok uiwait(msgbox(risk.internal.app.utils.getMessage(w.identifier),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:DuplicateCutPointsTitle'),'warn','modal')); obj.isDuplicateCutPoint = true; end [cp,isExit] = testForMonotonicity(cp); if isExit return; else obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CutPoints',cp); end end end function [cpOut,isExit] = testForMonotonicity(cpIn) isExit = false; if any(diff(cpIn)<0) Choice = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:EditEdgeValueMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:EditEdgeValueTitle'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes')); switch lower(Choice) case 'yes' if strcmpi(src.Name,'rightedge') Ind1 = (obj.OldCutPoints > EdgeValue); if Ind == obj.NumBins Ind2 = []; else Ind2 = Ind + find(~Ind1(Ind+1:end)); end Sign = 1; % Merge to the right else Ind1 = (cpIn >= EdgeValue); if Ind == 1 Ind2 = []; elseif Ind == obj.NumBins Ind2 = find(Ind1(1:Ind-2)); else Ind2 = find(Ind1(1:Ind-1)); end Sign = -1; % Merge to the left end cpIn(Ind2) = []; cpIn = cpIn(isfinite(cpIn)); obj.isMergeApplied = true; cpOut = cpIn; obj.NumMergedBins = Sign*abs(length(obj.OldCutPoints)-length(cpIn)+1); case 'no' cpOut = cpIn; obj.NumMergedBins = 0; isExit = true; return; end else cpOut = cpIn; end end end function [Flag,w] = isWarningTriggered(Str) % Helper function to test for the keyword given by "Str" % 'DuplicateCutPoints' : test for duplicate cutpoints % 'MinAboveData' : test for Min above data % 'MaxBelowData' : test for Max below data % 'MinAboveCutPoints' : test for cutpoint below the Min % 'MaxBelowCutPoints' : test for cutpoint above the Max % % If 'Flag' is true, then the warning for the message identifier % below is removed from the command line, and instead, % displayed in a message box. Flag = false; Msg = 'finance:internal:finance:binning:Numeric:'; switch lower(Str) case 'duplicatecutpoints' if ismember(EdgeValue,cp) || ... ((Ind == obj.NumBins) && ... (EdgeValue == obj.RightEdgeValue) && ... strcmpi(src.Name,'leftedge')) || ... ((Ind == 1) && ... (EdgeValue == obj.LeftEdgeValue) && ... strcmpi(src.Name,'rightedge')) w = warning('off',[Msg 'DuplicateCutPoints']); Flag = true; else w = []; end case 'minabovedata' if (EdgeValue > max(mv(1),Stats.Value(1))) && strcmpi(src.Name,'leftedge') w = warning('off',[Msg 'MinAboveData']); Flag = true; else w = []; end case 'maxbelowdata' if (EdgeValue < min(mv(2),Stats.Value(2))) && strcmpi(src.Name,'rightedge') w = warning('off',[Msg 'MaxBelowData']); Flag = true; else w = []; end case 'minabovecutpoints' if (EdgeValue >= min(cp)) && strcmpi(src.Name,'leftedge') w = warning('off',[Msg 'MinAboveCutPoints']); Flag = true; else w = []; end case 'maxbelowcutpoints' if (EdgeValue < max(cp)) && strcmpi(src.Name,'rightedge') w = warning('off',[Msg 'MaxBelowCutPoints']); Flag = true; else w = []; end case 'cutpointsbelowmin' if (max(mv(1),Stats.Value(1)) >= min(cp))% && strcmpi(src.Name,'leftedge') w = warning('off',[Msg 'CutPointsBelowMin']); Flag = true; else w = []; end case 'cutpointsabovemax' if (min(mv(2),Stats.Value(2)) <= max(cp)) %&& strcmpi(src.Name,'rightedge') w = warning('off',[Msg 'CutPointsAboveMax']); Flag = true; else w = []; end end end end function obj = splitBin(obj,varargin) % Callback on the Toolstrip button 'Split'. It splits the bin % in 'obj.SelectedBins' according to rules set interactively by % the user. Predictor = obj.SelectedPredictors{1}; % Bring up the interface if ismember(Predictor,obj.getNumericPredictors()) obj.splitNumericUI(); else obj.splitCategoricalUI(); end end function obj = splitNumericUI(obj) % Called by 'obj.splitBin'. Open a UI interface to allow the % user to split the selected bin into smaller bins. For numeric % predictors only. % Make sure the visibility is on set(groot,'DefaultFigureVisible','On'); InitNumBins = 2; obj.SplitBinLine = gobjects(obj.MaxNumBinsSplit,1); obj.CutPointBox = gobjects(obj.MaxNumBinsSplit,1); Predictor = obj.SelectedPredictors{1}; obj.SplitFigure = figure('Name',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Split'),... 'IntegerHandle','Off','HandleVisibility','Off','Units',... 'characters','MenuBar','None','Position',[84 17 80 35],... 'CloseRequestFcn',@obj.closeSplitWindow,'Resize','Off',... 'Tag','splitfigure','WindowStyle','modal'); movegui(obj.SplitFigure,'center'); hp = uipanel('Parent',obj.SplitFigure,'Units','Normalized',... 'Position',[0 0 1 1]); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OK'),... 'Position',[57 1 10 2],'Tag','OK','Callback',@obj.okSplitCallback); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Cancel'),... 'Position',[68 1 10 2],'Tag','Cancel','Callback',@obj.cancelSplitCallback); % Retrieve the data from the selected bin if isa(obj.OriginalData,'table') Data = obj.OriginalData.(Predictor); else Data = obj.OriginalData.Data.(Predictor); end % Predictor was converted from Categorical to Numeric if ismember(Predictor,obj.ModifiedPredictors) Data = double(categorical(Data)); end Data = Data(Data >= obj.LeftEdgeValue & Data < obj.RightEdgeValue); % Properly display bins and tick labels hax = axes('Parent',hp,'Visible','Off','Units','characters',... 'Position',[12 7 60 13]); hHist = histogram(hax,Data); drawnow; % Plot slider line to hold cutpoint values hax.XLimMode = 'manual'; hax.YLimMode = 'manual'; set(hax,'Clipping','off') hax.XLabel.String = ''; hax.YLabel.String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinCount'); hax.XTick = setdiff(hax.XTick,hHist.BinEdges,'stable'); text('Parent',hax,'Units','characters','Position',... [-9 18],'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:CutPoints'),... 'FontWeight','Demi'); text('Parent',hax,'Units','characters','Position',... [-4 15],'String',num2str(obj.LeftEdgeValue),'EdgeColor',... 'k','BackgroundColor',[0.8 0.8 0.8]); text('Parent',hax,'Units','characters','Position',... [61 15],'String',num2str(obj.RightEdgeValue),... 'EdgeColor','k','BackgroundColor',[0.8 0.8 0.8]); XPos = hHist.BinEdges(floor(hHist.NumBins/2)+1); NumWidth = max(2,floor(log10(XPos))); obj.SplitCutPoints = ceil(XPos); obj.SplitBinLine(1) = line([XPos XPos],... [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],... 'Parent',hax,'Tag','splitline1'); obj.CutPointBox(1) = uicontrol('Style','Edit','Parent',hp,... 'Units','characters','Position',[obj.SplitFigure.Position(3)/2-NumWidth 21 2*NumWidth 2],... 'String',num2str(ceil(XPos)),'Tag','cutpoint1',... 'Callback',{@(src,ev)obj.slideSplitLine(src,ev,hax)}); % Add patch to visually distinguish between new bins patch([hax.XLim(1) XPos XPos hax.XLim(1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,... 'Parent',hax,'FaceAlpha',0.1,'EdgeColor','none'); patch([XPos hax.XLim(2) hax.XLim(2) XPos],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,... 'Parent',hax,'FaceAlpha',0.2,'EdgeColor','none'); uistack(hHist,'top'); % Add the title and center it ht = uicontrol('Style','text','String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SplitNumericTitle',... num2str(obj.SelectedBins),Predictor),'Parent',hp,'Units',... 'characters','Position',[10 31.5 80 2],'FontWeight','Bold'); ht.Position(3) = ht.Extent(3); ht.Position(1) = hax.Position(1) +(hax.Position(3)-ht.Position(3))/2; % Add/subtract bin controls uicontrol('Style','text','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:NumberOfBins'),... 'Parent',hp,'Units','characters','Position',[-3 27 30 2],... 'FontWeight','Bold'); hEdit = uicontrol('Style','edit','Parent',hp,'Units',... 'characters','Position',[27 28 5 1],'String',... num2str(InitNumBins),'Tag','numbinsedit','Callback',... {@(src,ev)obj.addOrRemoveBinForSplitNumeric(src,ev,hax,hHist)}); pathstr = fileparts(mfilename('fullpath')); AddIcon = fullfile(pathstr,'icons','AddItem.png'); RemoveIcon = fullfile(pathstr,'icons','RemoveItem.png'); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','Position',[25 28 2 1],'tag','removebin','CData',... double(imread(RemoveIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,... 'Callback',{@(src,ev) obj.addOrRemoveBinForSplitNumeric(src,ev,hEdit,hax,hHist)}); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','Position',[32 28 2 1],'tag','addbin','CData',... double(imread(AddIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,... 'Callback',{@(src,ev) obj.addOrRemoveBinForSplitNumeric(src,ev,hEdit,hax,hHist)}); end function obj = splitCategoricalUI(obj) % Called by 'obj.splitBin'. Open a UI interface to allow the % user to split the selected bin into smaller bins. For % categorical predictors only. % Make sure the visibility is on set(groot,'DefaultFigureVisible','On'); InitNumBins = 2; obj.SplitBinLine = gobjects(obj.MaxNumBinsSplit,1); obj.CutPointBox = gobjects(obj.MaxNumBinsSplit,1); Predictor = obj.SelectedPredictors{1}; obj.SplitFigure = figure('Name',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Split'),... 'IntegerHandle','Off','HandleVisibility','Off','Units',... 'characters','MenuBar','None','Position',[84 17 80 35],... 'CloseRequestFcn',@obj.closeSplitWindow,'Resize','Off',... 'Tag','splitfigure','WindowStyle','modal'); movegui(obj.SplitFigure,'center'); hp = uipanel('Parent',obj.SplitFigure,'Units','Normalized','Position',... [0 0 1 1]); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:OK'),... 'Position',[57 0.5 10 2],'Tag','OK','Callback',@obj.okSplitCallback); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Cancel'),... 'Position',[68 0.5 10 2],'Tag','Cancel','Callback',@obj.cancelSplitCallback); % Retrieve the data from the selected bin [~,Info] = predictorinfo(obj.Scorecard,Predictor); [~,Ind] = ismember(obj.BinMembers,Info.Properties.RowNames); Counts = Info.Count(Ind); Data = cell(sum(Counts),1); for i = 1 : length(Ind) if i == 1 i1 = 1; i2 = Counts(i); else i1 = sum(Counts(1:i-1))+1; i2 = i1 - 1 + Counts(i); end Data(i1:i2,:) = repmat(Info.Properties.RowNames(Ind(i)),Counts(i),1); end % Properly display bins and tick labels hax = axes('Parent',hp,'Visible','Off','Units','characters',... 'Position',[15 16 60 11]); hHist = histogram(hax,categorical(Data),obj.BinMembers); drawnow; hax.YLabel.String = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinCount'); XPos = floor(length(obj.BinMembers)/2)+0.5; obj.SplitBinLine(1) = line([XPos XPos],... [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],... 'Parent',hax,'Tag','splitline1'); % Add patch to visually distinguish between new bins patch([hax.XLim(1) XPos XPos hax.XLim(1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,... 'Parent',hax,'FaceAlpha',0.1,'EdgeColor','none'); patch([XPos hax.XLim(2) hax.XLim(2) XPos],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,... 'Parent',hax,'FaceAlpha',0.2,'EdgeColor','none'); uistack(hHist,'top'); % Add the title and center it ht = uicontrol('Style','text','String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SplitCategoricalTitle',... num2str(obj.SelectedBins),Predictor),'Parent',hp,'Units',... 'characters','Position',[10 31.5 80 2],'FontWeight','Bold'); ht.Position(3) = ht.Extent(3); ht.Position(1) = hax.Position(1) +(hax.Position(3)-ht.Position(3))/2; % Add/subtract bin controls uicontrol('Style','text','String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:NumberOfBins'),... 'Parent',hp,'Units','characters','Position',[0 29.5 30 2],... 'FontWeight','Bold'); hEdit = uicontrol('Style','edit','Parent',hp,'Units',... 'characters','Position',[30 30.5 5 1],'String',... num2str(InitNumBins),'Tag','numbinsedit','Callback',... {@(src,ev)obj.addOrRemoveBinForSplitCategorical(src,ev,hax,hHist)}); pathstr = fileparts(mfilename('fullpath')); AddIcon = fullfile(pathstr,'icons','AddItem.png'); RemoveIcon = fullfile(pathstr,'icons','RemoveItem.png'); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','Position',[28 30.5 2 1],'tag','removebin','CData',... double(imread(RemoveIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,... 'Callback',{@(src,ev) obj.addOrRemoveBinForSplitCategorical(src,ev,hEdit,hax,hHist)}); uicontrol('Style','pushbutton','Parent',hp,'Units',... 'characters','Position',[35 30.5 2 1],'tag','addbin','CData',... double(imread(AddIcon,'BackgroundColor',[0.9 0.9 0.9]))/255,... 'Callback',{@(src,ev) obj.addOrRemoveBinForSplitCategorical(src,ev,hEdit,hax,hHist)}); % Bin listboxes uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','1'),... 'Position',[22 13 7 1],'fontweight','bold','Tag','bottombintext1'); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','2'),... 'Position',[52 13 7 1],'fontweight','bold','Tag','bottombintext2'); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','1'),... 'Position',[22 27 7 2],'fontweight','bold','Tag','topbintext1'); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber','2'),... 'Position',[52 27 7 2],'fontweight','bold','Tag','topbintext2'); hc1 = uicontrol('Style','listbox','Parent',hp,'String',... obj.BinMembers(1:floor(XPos)),'Units','characters',... 'Position',[17 5 17 6],'Max',floor(XPos),'Tag','listbox1',... 'Callback',@obj.storeSelectedCategories); hc2 = uicontrol('Style','listbox','Parent',hp,'String',... obj.BinMembers(ceil(XPos):end),'Units','characters',... 'Position',[55 5 17 6],'Max',length(obj.BinMembers)-... floor(XPos),'Tag','listbox2','Callback',@obj.storeSelectedCategories); % Store bin members obj.SplitCategories = cell(2,2); obj.SplitCategories(1,:) = {obj.BinMembers(1:floor(XPos)),1}; obj.SplitCategories(2,:) = {obj.BinMembers(ceil(XPos):end),2}; % Move between adjacent bins (arrows) uicontrol('Style','pushbutton','Parent',hp,'String','>',... 'Units','characters','Position',[42 8 5 1],'Callback',... {@(src,ev)obj.moveFromBinToBin(src,ev,hc1,hc2,hHist,hEdit)},... 'Tag','pushbutton1'); uicontrol('Style','pushbutton','Parent',hp,'String','<',... 'Units','characters','Position',[42 6.5 5 1],'Callback',... {@(src,ev)obj.moveFromBinToBin(src,ev,hc2,hc1,hHist,hEdit)},... 'Tag','pushbutton2'); % Select all & checkbox uicontrol('Style','checkbox','Parent',hp,'Units','characters',... 'Position',[17 11.5 15 1],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Tag',... 'checkbox1','Callback',{@(src,ev)obj.selectAllBinMembers(src,ev,hc1)}); uicontrol('Style','checkbox','Parent',hp,'Units','characters',... 'Position',[55 11.5 15 1],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Tag',... 'checkbox2','Callback',{@(src,ev)obj.selectAllBinMembers(src,ev,hc2)}); end function obj = addOrRemoveBinForSplitNumeric(obj,varargin) % Callback on the "plus" and "minus" pushbutton, as well as the % adjacent edit box. Each click on "plus" increases by one unit % the value displayed in the edit box and each clink on "minus" % decreases it by one. Maximum value is that corresponding to % the total number of categories or discrete numeric value (or % 5). Minimum value is 2. [hax,~,~,nBins] = obj.addOrRemoveBinsHelper(varargin{:}); % Remove, from right, a split line and cutpoint delete(obj.SplitBinLine(:)) delete(obj.CutPointBox(:)) delete(risk.internal.app.utils.extractChildren(hax,'patch')); XPos = zeros(nBins-1,1); obj.SplitCutPoints = zeros(nBins-1,1); if ~isfinite(obj.LeftEdgeValue) [~,T] = predictorinfo(obj.Scorecard,obj.SelectedPredictors{1}); obj.LeftEdgeValue = T.Value('Min'); elseif ~isfinite(obj.RightEdgeValue) [~,T] = predictorinfo(obj.Scorecard,obj.SelectedPredictors{1}); obj.RightEdgeValue = T.Value('Max'); end for i = 1 : (nBins-1) XPos(i) = obj.LeftEdgeValue + ... i*(obj.RightEdgeValue-obj.LeftEdgeValue-1)/nBins; NumWidth = max(2,floor(log10(XPos(i)))); if mod(XPos(i),1) == 0 cpText = num2str(XPos(i)+1); else cpText = num2str(ceil(XPos(i))); end obj.SplitCutPoints(i) = str2double(cpText); obj.SplitBinLine(i) = line(floor([XPos(i) XPos(i)])+0.5,... [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],... 'Parent',hax,'tag',['splitline' num2str(i)]); obj.CutPointBox(i) = uicontrol('Style','Edit','Parent',... hax.Parent,'Units','characters','Position',... [hax.Position(1)+i/nBins*hax.Position(3)-NumWidth ... 21 2*NumWidth 2],'String',cpText,'Tag',['cutpoint' ... num2str(i)],'Callback',{@(src,ev)obj.slideSplitLine(src,ev,hax)}); % Face alpha for patches if mod(i,2) == 1 fa = 0.1; else fa = 0.2; end if i == 1 patch([hax.XLim(1) floor(XPos(i))+0.5 floor(XPos(i))+0.5 hax.XLim(1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',... hax,'FaceAlpha',fa,'EdgeColor','none'); else patch([floor(XPos(i-1)) floor(XPos(i)) floor(XPos(i)) ... floor(XPos(i-1))]+0.5,[0 0 hax.YLim(2) hax.YLim(2)],... obj.PatchColor,'Parent',hax,'FaceAlpha',fa,... 'EdgeColor','none'); end end patch([floor(XPos(nBins-1))+0.5 hax.XLim(2) hax.XLim(2)... floor(XPos(nBins-1))+0.5],[0 0 hax.YLim(2) ... hax.YLim(2)],obj.PatchColor,'Parent',hax,... 'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none'); end function obj = addOrRemoveBinForSplitCategorical(obj,varargin) % Callback on the "plus" and "minus" pushbutton, as well as the % adjacent edit box. Each click on "plus" increases by one unit % the value displayed in the edit box and each clink on "minus" % decreases it by one. Maximum value is that corresponding to % the total number of categories or discrete numeric value (or % 5). Minimum value is 2. [hax,hHist,hEdit,nBins] = obj.addOrRemoveBinsHelper(varargin{:}); % Remove, from right, a split line and cutpoint hf = obj.SplitFigure; hp = risk.internal.app.utils.extractChildren(hf,'uipanel'); delete(obj.SplitBinLine(:)) delete(risk.internal.app.utils.extractChildren(hax,'patch')); delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','listbox')); delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','bintext')); delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','pushbutton')); delete(risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','checkbox')); XPos = zeros(nBins-1,1); hc = cell(nBins,1); obj.SplitCategories = cell(nBins,2); AvgNumBins = floor(length(hHist.Categories)/nBins); % Update the bin listing in the popupmenu obj.DefaultBinListing = cell(nBins+1,1); obj.DefaultBinListing{1} = {''}; for i = 1 : nBins obj.DefaultBinListing{i+1} = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',num2str(i)); end x0 = hax.Position(1); y = 4; w = hax.Position(3)/(2*nBins-1); h = 6; for i = 1 : (nBins-1) XPos(i) = i*AvgNumBins+0.5; obj.SplitBinLine(i) = line([XPos(i) XPos(i)],... [0 hax.YLim(2)],'LineStyle','--','Color',[0 0 0],... 'Parent',hax,'tag',['splitline' num2str(i)]); if mod(i,2) == 1 fa = 0.1; else fa = 0.2; end if i == 1 Members = hHist.Categories(1:floor(XPos(i))); patch([hax.XLim(1) XPos(i) XPos(i) hax.XLim(1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',... hax,'FaceAlpha',fa,'EdgeColor','none'); else Members = hHist.Categories(floor(XPos(i-1)+1:XPos(i))); patch([XPos(i-1) XPos(i) XPos(i) XPos(i-1)],... [0 0 hax.YLim(2) hax.YLim(2)],... obj.PatchColor,'Parent',hax,'FaceAlpha',fa,... 'EdgeColor','none'); end % Place bin number on top of the histogram. Transform XLim % units to character units xtxt = obj.convertXLimToCharacter(i,XPos,x0,hax,true); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',... num2str(i)),'Position',[xtxt 27 7 1],'Tag',... ['topbintext' num2str(i)],'fontweight','bold'); % Create listboxes and checkboxes hc{i} = uicontrol('Style','listbox','Parent',hp,'String',... Members,'Units','characters','Position',[x0+2*(i-1)*w y w h],... 'Max',length(Members),'Tag',['listbox' num2str(i)],... 'Callback',@obj.storeSelectedCategories); uicontrol('Style','checkbox','Parent',hp,'Units','characters',... 'Position',[x0+2*(i-1)*w y+h+0.5 15 1],'Tag',... ['checkbox' num2str(i)],'String',.... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Callback',... {@(src,ev)obj.selectAllBinMembers(src,ev,hc{i})}); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',... num2str(i)),'Position',[xtxt y+h+3 7 1],'Tag',... 'bottombintext','fontweight','bold'); % Store the members and their corresponding bin number obj.SplitCategories(i,:) = {Members,i}; end Members = hHist.Categories(floor(XPos(nBins-1))+1:end); obj.SplitCategories(nBins,:) = {Members,nBins}; patch([XPos(nBins-1) hax.XLim(2) hax.XLim(2) XPos(nBins-1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',hax,... 'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none'); hc{nBins} = uicontrol('Style','listbox','Parent',hp,'String',... Members,'Units','characters','Position',[x0+2*(nBins-1)*w y w h],... 'Max',length(Members),'Tag',['listbox' num2str(nBins)],... 'Callback',@obj.storeSelectedCategories); uicontrol('Style','checkbox','Parent',hp,'Units','characters',... 'Position',[x0+2*(nBins-1)*w y+h+0.5 15 1],'Tag',... ['checkbox' num2str(nBins)],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SelectAll'),'Callback',... {@(src,ev)obj.selectAllBinMembers(src,ev,hc{nBins})}); % Bin title: displayed twice xtxt = obj.convertXLimToCharacter(nBins,XPos,x0,hax,false); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',... num2str(nBins)),'Position',[xtxt y+h+3 7 1],'Tag',... 'bottombintext','fontweight','bold'); uicontrol('Style','Text','Parent',hp,'Units','characters',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumber',... num2str(nBins)),'Position',[xtxt 27 7 1],'Tag',... ['topbintext' num2str(nBins)],'fontweight','bold'); % Create pushbuttons for interbin member transfer for i = 1 : (nBins - 1) Pos1 = [x0+(2*i-1)*w+(w-5)/2 7 5 1]; Pos2 = [x0+(2*i-1)*w+(w-5)/2 5.5 5 1]; uicontrol('Style','pushbutton','Parent',hp,'String','>',... 'Units','characters','Position',Pos1,'Callback',... {@(src,ev)obj.moveFromBinToBin(src,ev,hc{i},hc{i+1},hHist,hEdit)},... 'Tag',['pushbutton' num2str(i) '.1']); uicontrol('Style','pushbutton','Parent',hp,'String','<',... 'Units','characters','Position',Pos2,'Callback',... {@(src,ev)obj.moveFromBinToBin(src,ev,hc{i+1},hc{i},hHist,hEdit)},... 'Tag',['pushbutton' num2str(i) '.2']); end obj.storeSelectedCategories(hc{1},[]); end function [hax,hHist,hEdit,nBins] = addOrRemoveBinsHelper(obj,varargin) % Helper function called by 'addOrRemoveBinForSplitCategorical' % and 'addOrRemoveBinForSplitNumeric' src = varargin{1}; if nargin == 6 hEdit = varargin{3}; hax = varargin{4}; else hEdit = src; hax = varargin{3}; end hHist = varargin{end}; nBins = str2double(hEdit.String); if ismember('numbins',lower(properties(hHist))) % Numeric N = hHist.NumBins; else N = length(hHist.Categories); % Categorical end if strcmpi(src.Tag,'removebin') if (nBins > 2 ) nBins = nBins - 1; obj.OldNumBins = nBins; else return; end elseif strcmpi(src.Tag,'addbin') if (nBins < obj.MaxNumBinsSplit + 1) && (nBins+1<= N) nBins = nBins + 1; obj.OldNumBins = nBins; else return; end else if nBins > obj.MaxNumBinsSplit + 1 msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:MaxNumBinsSplitMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:MaxNumBinsSplitTitle'),'warn','modal'); nBins = obj.OldNumBins; elseif nBins < 2 msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:MinNumBinsSplitMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:MinNumBinsSplitTitle'),'warn','modal'); nBins = obj.OldNumBins; elseif mod(nBins,1) ~= 0 msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitTitle'),'warn','modal'); nBins = obj.OldNumBins; elseif nBins > N%~ismember('numbins',lower(properties(hHist))) && nBins > length(hHist.Categories) msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitCategoricalMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidNumBinsSplitTitle'),'warn','modal'); nBins = obj.OldNumBins; end end hEdit.String = num2str(nBins); obj.OldNumBins = nBins; end function obj = slideSplitLine(obj,src,~,hax) % Callback on the cutpoint edit boxes. When the user enters a % new value, the split line is shifted accordingly obj.InvalidCutPoint = false; Tag = src.Tag; Num = str2double(Tag(length('cutpoint')+1:end)); Value = str2double(src.String); BinWidth = 0.5; % Extract non empty graphic objects in obj.SplitBinLine Ind1 = arrayfun(@(c)~isempty(fieldnames(c)),obj.SplitBinLine); Ind2 = isvalid(obj.SplitBinLine); hLines = obj.SplitBinLine(Ind1&Ind2); % Make sure the edited cutpoint is valid TempInd = obj.SplitCutPoints; TempInd(Num) = Value; % NaN or Inf cutpoints if any(isnan(TempInd)) || any(~isfinite(TempInd)) obj.InvalidCutPoint = true; msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InfiniteNaNCutPointsMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:InfiniteNaNCutPointsTitle'),'warn','modal'); src.String = num2str(obj.SplitCutPoints(Num)); return; end % Duplicate cutpoints if any(diff(TempInd) == 0) msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:DuplicateCutPointsMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:DuplicateCutPointsTitle'),'warn','modal'); end % Non-increasing cutpoints if any(diff(TempInd)<0) || (Value <= obj.LeftEdgeValue) || (Value >= obj.RightEdgeValue) obj.InvalidCutPoint = true; msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsMsg3',... Value,obj.LeftEdgeValue,obj.RightEdgeValue),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ShiftCutPointsTitle'),'warn','modal'); src.String = num2str(obj.SplitCutPoints(Num)); return; else delete(risk.internal.app.utils.extractChildren(hax,'patch')); if Num > 1 XLeftLine = unique(obj.SplitBinLine(Num-1).XData-BinWidth); if Num < length(hLines) XRightLine = unique(obj.SplitBinLine(Num+1).XData); else XRightLine = obj.RightEdgeValue; end if (Value > XLeftLine) && (Value < XRightLine) obj.SplitBinLine(Num).XData = [Value Value]-BinWidth; end else obj.SplitBinLine(Num).XData = [Value Value]-BinWidth; end % Update the patch updatePatchAreas() obj.SplitCutPoints(Num) = Value; obj.InvalidCutPoint = false; end function updatePatchAreas() % Once the split line is shifted, the patch regions must % be updated nBins = length(hLines)+1; XPos = zeros(nBins-1,1); for i = 1 : (nBins-1) if mod(i,2) == 1 fa = 0.1; else fa = 0.2; end XPos(i) = unique(obj.SplitBinLine(i).XData); if i == 1 patch([hax.XLim(1) XPos(i) XPos(i) hax.XLim(1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',... hax,'FaceAlpha',fa,'EdgeColor','none'); else patch([XPos(i-1) XPos(i) XPos(i) XPos(i-1)],... [0 0 hax.YLim(2) hax.YLim(2)],... obj.PatchColor,'Parent',hax,'FaceAlpha',fa,... 'EdgeColor','none'); end end patch([XPos(nBins-1) hax.XLim(2) hax.XLim(2) XPos(nBins-1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',... hax,'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none'); end end function obj = storeSelectedCategories(obj,varargin) % Callback on the listbox in the split window for categorical % predictors. It stores the selected categories into the % object's property to move to another bin src = varargin{1}; N = src.Tag(length('listbox')+1:end); if isempty(src.String) obj.SelectedCategories = {N;''}; else obj.SelectedCategories = [N;src.String(src.Value)]; end % If the corresponding checkbox is selected, deselect it, if % less than the total elements are selected. Num = src.Tag(length('listbox')+1:end); hp = risk.internal.app.utils.extractChildren(obj.SplitFigure,'uipanel'); hCheck = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag',['checkbox' Num]); if (hCheck.Value == 1) && (length(src.Value)<length(src.String)) hCheck.Value = 0; end end function obj = selectAllBinMembers(obj,varargin) % Callback on the checkbox to select all elements in the % listbox src = varargin{1}; hListbox = varargin{3}; if src.Value == 1 hListbox.Value = 1:hListbox.Max; obj.storeSelectedCategories(hListbox,[]); else hListbox.Value = 1; end end function obj = moveFromBinToBin(obj,varargin) % Callback on the arrowed pushbutton to move the selected item % in the listbox from one bin to the adjacent. % Step 0: Deselect any "Select all" checkbox hp = risk.internal.app.utils.extractChildren(obj.SplitFigure,'uipanel'); hCheck = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','checkbox'); set(hCheck,'Value',0); % Step 1: Move elements between bins hGive = varargin{3}; hGet = varargin{4}; hHist = varargin{5}; nBins = str2double(varargin{6}.String); N1 = str2double(hGive.Tag(length('listbox')+1:end)); N2 = str2double(hGet.Tag(length('listbox')+1:end)); if N1 < N2 LeftBin = hGive; RightBin = hGet; else LeftBin = hGet; RightBin = hGive; end List1 = cellstr(hGive.String); List2 = cellstr(hGet.String); List1(strcmpi('',List1)) = []; List2(strcmpi('',List2)) = []; Values = 1 : length(List1); [~,Ind] = ismember(hGive.Value,Values); if all(Ind) > 0 if isempty(hGet.Value) hGet.Value = 1; end Values(Ind) = []; hGet.String = [List2; List1(hGive.Value)]; hGive.Value = 1; hGive.String = hGive.String(Values); hGive.Max = length(hGive.String); hGet.Max = length(hGet.String); else return; end % Step 2: Update the bin elements and patches. % Sort categories from left to right bin. Find which is left % bin by finding which bin the first element in the histogram % belongs to. Categories = [LeftBin.String;RightBin.String]; [ok,Ind] = ismember(hHist.Categories,Categories); if ok(1) hHist.Categories = [Categories;hHist.Categories(Ind==0)']; else IndElemInCategory = find(Ind); FirstNNZhalf = 1:(IndElemInCategory(1)-1); SecondNNZhalf = (IndElemInCategory(end)+1):length(Ind); hHist.Categories = [hHist.Categories(FirstNNZhalf)';... Categories;hHist.Categories(SecondNNZhalf)']; end hax = hHist.Parent; delete(risk.internal.app.utils.extractChildren(hax,'patch')); if nBins > 2 % Find which split line to update Num = str2double(LeftBin.Tag(length('listbox')+1:end)); Ind1 = arrayfun(@(c)~isempty(fieldnames(c)),obj.SplitBinLine); Ind2 = isvalid(obj.SplitBinLine); hLines = obj.SplitBinLine(Ind1&Ind2); XPos = arrayfun(@(c)unique(c.XData),hLines); try XPos(Num) = length(LeftBin.String)+length(FirstNNZhalf)+0.5; catch XPos(Num) = length(LeftBin.String)+0.5; end obj.SplitBinLine(Num).XData = [XPos(Num) XPos(Num)]; else Num = 1; XPos = length(LeftBin.String)+0.5; obj.SplitBinLine(1).XData = [XPos XPos]; end % Store selected members if not previously defined. This % happens if the user did not actively click to select the % element to move. if isempty(obj.SelectedCategories) if isempty(hGive.String) obj.SelectedCategories = {N1;''}; else obj.SelectedCategories = [N1;hGive.String(hGive.Value)]; end end % Store the bin members storeBinMembers(); % Update the patches updatePatchAreas(); % Update the position of the 'topbintext' text object htxt1 = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag',['topbintext' num2str(Num)]); htxt2 = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag',['topbintext' num2str(Num+1)]); x1 = obj.convertXLimToCharacter(Num,XPos,... hHist.Parent.Position(1),hHist.Parent,true); x2 = obj.convertXLimToCharacter(Num+1,XPos,... hHist.Parent.Position(1),hHist.Parent,~(Num+1==nBins)); htxt1.Position(1) = x1; htxt2.Position(1) = x2; % Reset Selected Categories to force user selection obj.SelectedCategories = []; function updatePatchAreas() % Once the split line is shifted, the patch regions must % be updated hax = hHist.Parent; for i = 1 : (nBins-1) if mod(i,2) == 1 fa = 0.1; else fa = 0.2; end if i == 1 patch([hax.XLim(1) XPos(i) XPos(i) hax.XLim(1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',... hax,'FaceAlpha',fa,'EdgeColor','none'); else patch([XPos(i-1) XPos(i) XPos(i) XPos(i-1)],... [0 0 hax.YLim(2) hax.YLim(2)],... obj.PatchColor,'Parent',hax,'FaceAlpha',fa,... 'EdgeColor','none'); end end patch([XPos(nBins-1) hax.XLim(2) hax.XLim(2) XPos(nBins-1)],... [0 0 hax.YLim(2) hax.YLim(2)],obj.PatchColor,'Parent',... hax,'FaceAlpha',abs(0.2-fa)+0.1,'EdgeColor','none'); end function storeBinMembers() % Find the listboxes. % Extract the corresponding bin number and sort in ascending order. % Extract the bin member and associate to the bin number hList = risk.internal.app.utils.extractChildren(hp,'uicontrol','Tag','listbox'); Indices = arrayfun(@(c)str2double(c.Tag(length('listbox')+1:end)),hList); [~,ii] = sort(Indices); hList = hList(ii); obj.SplitCategories = cell(length(hList),2); for i = 1 : length(hList) obj.SplitCategories(i,:) = {hList(i).String,i}; end end end function obj = cancelSplitCallback(obj,varargin) % Callback on the "Cancel" button in the "Split" window. It % cancels the changes and closes the "Split" window. obj.closeSplitWindow(obj,varargin{:}); end function obj = okSplitCallback(obj,varargin) % Callback on the "OK" button. Accepts the split rules and % close the window. if obj.InvalidCutPoint obj.InvalidCutPoint = false; return; end Predictor = obj.SelectedPredictors{1}; % 2. Store old cutpoints / category groupings if ismember(Predictor,obj.getNumericPredictors()) [~,cp] = bininfo(obj.Scorecard,Predictor); obj.OldCutPoints = cp; cp = sort([cp; unique(obj.SplitCutPoints)]); else [~,cg] = bininfo(obj.Scorecard,Predictor); obj.OldCatGroupings = cg; T = formatSplitCategories(); [~,Ind1] = ismember(T.Category,cg.Category); Ind2 = (cg.BinNumber > obj.SelectedBins); % Re-arrange the bin numbering for the new categories. % If k = obj.SelectedBins, then: % 1. Bins 1..(k-1) : unchanged % 2. Bin k becomes k, k+1,...,k+(m-1), "m" being the % number of new sub-bins % 3. Bins above k become j = j+k+(m-1). cg.BinNumber(Ind1) = T.BinNumber; cg.BinNumber(Ind2) = cg.BinNumber(Ind2) + ... length(obj.SplitCategories)-1; end % 3. Update scorecard if ismember(Predictor,obj.getNumericPredictors()) obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CutPoints',cp); else if any(diff(cg.BinNumber) < 0) && ... ismember(Predictor,obj.getOrdinalPredictors()) w = warning('off','finance:internal:finance:binning:Categorical:NonMonotonicGrouping'); c = onCleanup(@() warning(w)); uiwait(msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalSplitNonAdjacentMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:OrdinalSplitNonAdjacentTitle'),'warn','modal')); end obj.Scorecard = modifybins(obj.Scorecard,Predictor,'CatGrouping',cg); end obj.isScorecardExported = false; obj.isSplitApplied = true; % 4. Update the zoomed and overview figures obj.isPlotChanged = true; notify(obj,'SelectedFigureTab'); obj.isPlotChanged = false; % 5. Update statistic information obj.setBinInfo(); obj.setPredictorInfo(); % 6. Bring the zoomed figure to the foreground figure(obj.ZoomedFigure) obj.isSplitApplied = false; obj.closeSplitWindow(obj,varargin{:}); % Helper function: Format SplitCategories function T = formatSplitCategories() C1 = cell(length(obj.BinMembers),1); C2 = zeros(length(obj.BinMembers),1); T = table(C1,C2,'VariableNames',... {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Category'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinNumberTable')}); BinSize = cellfun(@(c)length(c),obj.SplitCategories(:,1)); for i = 1 : size(obj.SplitCategories,1) BinNum = obj.SplitCategories{i,2}; if i == 1 i1 = 1; i2 = BinSize(i); else i1 = 1 + sum(BinSize(1:i-1)); i2 = i1 - 1 + BinSize(i); end T.Category(i1:i2) = obj.SplitCategories{i,1}; T.BinNumber(i1:i2) = obj.SelectedBins - 1 + BinNum;%/10; end end end function obj = closeSplitWindow(obj,varargin) % Callback on the CloseRequestFcn of the "Split" figure. Also, % called by 'obj.cancelSplitCallback' and 'obj.okSplitCallback' src = varargin{1}; if isa(src,'matlab.ui.Figure') delete(src); else delete(obj.SplitFigure); end end function xtxt = convertXLimToCharacter(~,i,XPos,x0,hax,tag) % Converts units between XLim of the current axes and character % units to position the bin number string atop the histogram. if i == 1 x_ = hax.XLim(1); else x_ = XPos(i-1); end if tag dx = (XPos(i)-x_)*hax.Position(3)/diff(hax.XLim); xi = x0 + (XPos(i)-hax.XLim(1))*hax.Position(3)/diff(hax.XLim); else dx = (hax.XLim(2)-x_)*hax.Position(3)/diff(hax.XLim); xi = x0 + hax.Position(3); end xtxt = xi - ((dx-7)/2 + 7); end %% Plot Section function section = LocalPlotSection(obj,TabName) % 1. Plot button PlotBtn = toolpack.component.TSDropDownButton(); PlotBtn.Name = 'plotoptionsbutton'; PlotBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PlotOptionsTooltip'); PlotBtn.Popup = obj.setPlotOptionsPopup('text_only'); if obj.isInitPlot PlotBtn.Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:None'); else % This is the manual tab PlotBtn.Text = obj.CtrlsOverview.PlotButtons(1).Text; end % 2. WOE Checkbox WOEBtn = toolpack.component.TSCheckBox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOECurve')); WOEBtn.Name = 'woecurvecheckbox'; WOEBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOECurveCheckboxTooltip'); if strcmpi(TabName,'manual') WOEBtn.Selected = obj.CtrlsOverview.PlotButtons(2).Selected; else WOEBtn.Selected = true; end % 3. Tooltip Checkbox (Manual only) if strcmpi(TabName,'manual') TooltipBtn = toolpack.component.TSCheckBox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:Datatip')); TooltipBtn.Name = 'datatipcheckbox'; TooltipBtn.Selected = true; TooltipBtn.Description = risk.internal.app.utils.getMessage('risk:binningexplorerapp:DatatipCheckboxTooltip'); end section = toolpack.component.TSPanel('f:p,1dlu','f:d,1dlu,f:p,1dlu,f:d,1dlu,f:d'); section.Name = 'PlotSection'; section.add(PlotBtn,'xy(1,3,''c,c'')'); section.add(WOEBtn,'xy(1,5,''l,c'')'); if strcmpi(TabName,'manual') section.add(TooltipBtn,'xy(1,7,''l,c'')'); obj.ActionListeners.Tooltip = addlistener(TooltipBtn,'ItemStateChanged',@obj.setBinTooltip); end % Callbacks on WOE checkbox and dropdown list obj.ActionListeners.PlotOptions = addlistener(PlotBtn.Popup,... 'ListItemSelected',@(src,ev)obj.setBinText(src,ev,TabName)); obj.ActionListeners.SetWOE = addlistener(WOEBtn,... 'ItemStateChanged',@(src,ev)obj.setWOEcurve(src,ev,TabName)); if strcmpi(TabName,'overview') obj.CtrlsOverview.PlotButtons = [PlotBtn;WOEBtn]; else obj.CtrlsManual.PlotButtons = [PlotBtn;WOEBtn;TooltipBtn]; end end function popup = setPlotOptionsPopup(~,TextOption) % Callback on the Plot Options dropdown button. Creates a drop % down list with five plot options. listItems = struct( ... 'Title', '', ... 'Style', '', ... 'Header', cell(5,1)); % None listItems(1).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:None'); listItems(1).Style = TextOption; listItems(1).Header = false; % Bin Count listItems(2).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Bincount'); listItems(2).Style = TextOption; listItems(2).Header = false; % Bin Level listItems(3).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinLevel'); listItems(3).Style = TextOption; listItems(3).Header = false; % Data Level listItems(4).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataLevel'); listItems(4).Style = TextOption; listItems(4).Header = false; % Total Count listItems(5).Title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:TotalCount'); listItems(5).Style = TextOption; listItems(5).Header = false; popup = toolpack.component.TSDropDownPopup(listItems,TextOption); popup.Name = 'plotoptionspopup'; end function obj = setBinText(obj,varargin) % Callback on the dropdown list (popup) for plot options. % Allows the user to choose how data are displayed on the % histogram: Stacked histogram, bin count, count of Goods and % Bads as a percentage of the data, or of the bins. if obj.isInitPlot return; end src = varargin{1}; TabName = varargin{3}; % Retrieve the dropdown handle if strcmpi(TabName,{'overview'}) CurrentDropDown = obj.CtrlsOverview.PlotButtons(1); if obj.isViewExpanded AltTabDropDown = obj.CtrlsManual.PlotButtons(1); else AltTabDropDown = []; end else CurrentDropDown = obj.CtrlsManual.PlotButtons(1); AltTabDropDown = obj.CtrlsOverview.PlotButtons(1); end idx = src.SelectedIndex; Value = src.Items(idx).Title; CurrentDropDown.Text = Value; % Update the other tab's button if ~isempty(AltTabDropDown) AltTabDropDown.Text = Value; end % Display the bin count information switch lower(Value) case 'no labels' obj.PlotSettings.BinText = 'none'; case 'bin count' obj.PlotSettings.BinText = 'count'; case '% data level' obj.PlotSettings.BinText = 'percentcols'; case '% bin level' obj.PlotSettings.BinText = 'percentrows'; case '% total count' obj.PlotSettings.BinText = 'percenttotal'; end obj.isBinTextChanged = true; notify(obj,'PlotSettingsChanged'); obj.isBinTextChanged = false; end function obj = setWOEcurve(obj,varargin) % Callback on the WOE checkbox if obj.isInitPlot || obj.isWOEChanged return; end src = varargin{1}; TabName = varargin{3}; % Retrieve the dropdown handle CurrentCheckbox = src; if strcmpi(TabName,{'overview'}) if obj.isViewExpanded AltCheckbox = obj.CtrlsManual.PlotButtons(2); else AltCheckbox = []; end else AltCheckbox = obj.CtrlsOverview.PlotButtons(2); end if CurrentCheckbox.Selected obj.PlotSettings.WOE = 'On'; else obj.PlotSettings.WOE = 'Off'; end obj.isWOEChanged = true; notify(obj,'PlotSettingsChanged'); % Update the checkbox on the other tab AltCheckbox.Selected = CurrentCheckbox.Selected; obj.isWOEChanged = false; end function obj = setBinTooltip(obj,varargin) % Callback on the listener to the "Tooltip" checkbox under % "Plot Options". It is a wrapper on the WindowButtonMotionFcn % of the selected axes. src = varargin{1}; if src.Selected obj.TooltipOn = true; else obj.TooltipOn = false; end end function obj = applyPlotSettings(obj,varargin) % Listener/Callback on the event 'PlotSettingsChanged'. This % method retrieves the plot settings and updates the plots. obj.isPlotChanged = true; obj.setMainFigureLayout('allpredictors'); obj.isPlotChanged = false; end function obj = showBinInformation(obj,varargin) % Callback on the 'WindowButtonMotionFcn' property of the % 'obj.ZoomedFigure'. It shows the datatip information about % the bin that the pointer hovers on. % The data property 'StartTime' is used to decide when to show % the tooltip and when to hide it. If the time elapsed between % two mouse moves is more than two seconds, the tooltip is % hidden. starttime = getappdata(obj.ZoomedFigure,'StartTime'); if ~isempty(starttime) && etime(clock,starttime)>2 && ... ~isempty(obj.TooltipHandleBins) && isvalid(obj.TooltipHandleBins) obj.TooltipHandleBins.Visible = 'off'; end if obj.TooltipOn Predictor = obj.SelectedPredictors{1}; hax = obj.ZoomedAxes; hFig = obj.ZoomedFigure; ht = risk.internal.app.utils.extractChildren(hFig,'uicontrol','Tag','bindatatip'); if ~isempty(ht) ht.Visible = 'Off'; end obj.setBinEdgesAndNumber(hax); hax.Units = 'characters'; hFig.Units = 'characters'; XPos = hax.CurrentPoint(1,1); YPos = hax.CurrentPoint(1,2); BinNumber = find(XPos >= obj.LeftEdgesXData & XPos < obj.RightEdgesXData); if ~isempty(BinNumber) %%%%% test if ~isempty or isa(handle)&& isvalid YBins = sum(cell2mat({obj.BarHandles.YData}')); if (YPos >= 0) && (YPos <= YBins(BinNumber)) if ~isempty(obj.OldBinNumber) && (BinNumber == obj.OldBinNumber) try obj.TooltipHandleBins.Visible = 'On'; catch end return; end % Convert from "histogram" units to characters. % Make sure to convert axes units to character to % d: distance between two X ticks % Set the y-position to the maximum bin height dh = 15; dw = 5; d = hax.Position(3)/length(obj.LeftEdgesXData); X = hax.Position(1) + (BinNumber-1+0.1)*d; Y = hax.Position(2) + hax.Position(4) - dh; bi = bininfo(obj.Scorecard,obj.SelectedPredictors{1}); line1 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:GoodInfo',num2str(bi.Good(BinNumber))); line2 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BadInfo',num2str(bi.Bad(BinNumber))); line3 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:OddsInfo',num2str(bi.Odds(BinNumber))); line4 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOEInfo',num2str(bi.WOE(BinNumber))); if ismember(Predictor,obj.getCategoricalPredictors()) [~,cg] = bininfo(obj.Scorecard,Predictor); Ind = (cg.BinNumber == BinNumber); Members = cg.Category(Ind); line5 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Members'); if length(Members) == 1 line6 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{1}); elseif length(Members) == 2 line6 = [risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{1}) ', '... risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{2})]; else line6 = [risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{1}) ', '... risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinMembers',Members{2}) ' ...']; end else line5 = []; line6 = []; end StringInfo = {line1;line2;line3;line4;line5;line6}; StringInfo = StringInfo(~cellfun('isempty',StringInfo)); if isempty(obj.TooltipHandleBins) || ~isvalid(obj.TooltipHandleBins) ht = uicontrol('Style','Text','Parent',hFig,... 'Units','characters','Position',[X Y dw dh],... 'BackgroundColor',[255 255 224]/255,'String',StringInfo,... 'HorizontalAlignment','left','Tag','bindatatip',... 'HitTest','Off'); obj.TooltipHandleBins = ht; else obj.TooltipHandleBins.String = StringInfo; obj.TooltipHandleBins.Position = [X Y dw dh]; end setappdata(obj.ZoomedFigure,'StartTime',clock) obj.TooltipHandleBins.Visible = 'On'; obj.TooltipHandleBins.Position(3) = obj.TooltipHandleBins.Extent(3) + 1; obj.TooltipHandleBins.Position(4) = obj.TooltipHandleBins.Extent(4) + 1; uistack(obj.TooltipHandleBins,'top'); obj.OldBinNumber = BinNumber; else % Outside bin range if ~isempty(obj.TooltipHandleBins) && isvalid(obj.TooltipHandleBins) obj.TooltipHandleBins.Visible = 'Off'; end setappdata(obj.ZoomedFigure,'StartTime',[]) end else % Outside and in between bins if ~isempty(obj.TooltipHandleBins) && isvalid(obj.TooltipHandleBins) obj.TooltipHandleBins.Visible = 'Off'; end end end end function StringInfo = getStringPredictorInfo(obj) Predictor = obj.SelectedPredictors{1}; PredInfo = predictorinfo(obj.Scorecard,Predictor); if ismember(Predictor,obj.getNumericPredictors()) PredType = 'Numeric'; else PredType = 'Categorical'; end line1 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredName',Predictor); line2 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredType',PredType); line3 = risk.internal.app.utils.getMessage('risk:binningexplorerapp:LatestBinning',PredInfo.LatestBinning{:}); StringInfo = {line1;line2;line3}; end %% Table Section function section = LocalTableSection(obj,TabName) TableOptions = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Odds');... risk.internal.app.utils.getMessage('risk:binningexplorerapp:WOE');... risk.internal.app.utils.getMessage('risk:binningexplorerapp:InfoValue');... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Entropy');... risk.internal.app.utils.getMessage('risk:binningexplorerapp:MembersTable')}; RowSpec = ['f:d,5dlu,' repmat('f:d,3dlu,',[1 3]),'1px']; section = toolpack.component.TSPanel('f:p,3dlu,f:p',RowSpec); section.Name = 'tableoptionpanel'; TableOptionBtn = cell(length(TableOptions),1); for i = 1 : length(TableOptions) TableOptionBtn{i} = toolpack.component.TSCheckBox(TableOptions{i}); TableOptionBtn{i}.Name = [lower(TableOptions{i}) 'checkbox']; TableOptionBtn{i}.Description = risk.internal.app.utils.getMessage(['risk:binningexplorerapp:' TableOptions{i} 'Checkbox' 'Tooltip']); if ismember(lower(TableOptions{i}),{'odds';'woe';'infovalue'}) TableOptionBtn{i}.Selected = true; end if strcmpi(TableOptions{i},'members') TableOptionBtn{i}.Enabled = false; end if i <= 3 Location = sprintf('xy(1,%d)',2*i+1); else Location = sprintf('xy(3,%d)',2*i-length(TableOptions)); end section.add(TableOptionBtn{i},Location); obj.ActionListeners.SetTableView = addlistener(TableOptionBtn{i},'ItemStateChanged',... @(src,ev)obj.setTableView(src,ev,TabName)); end if strcmpi(TabName,'manual') TableOptionBtn{end}.Enabled = obj.CtrlsOverview.TableButtons(end).Enabled; TableOptionBtn{end}.Selected = obj.CtrlsOverview.TableButtons(end).Selected; end if strcmpi(TabName,'overview') obj.CtrlsOverview.TableButtons = [TableOptionBtn{:}]'; else obj.CtrlsManual.TableButtons = [TableOptionBtn{:}]'; end end function obj = setTableView(obj,varargin) % Callback on each of the radio buttons under 'Table Options'. % Allows the user to select which statistics to display in the % Bin Information section of the Aap. if obj.isInitTable || obj.isTableViewChanged return; end src = varargin{1}; TabName = varargin{3}; Value = lower(src.Text); if src.Selected if ~strcmpi(Value,'members') A = unique([obj.TableSettings Value],'stable'); [~,Ind] = ismember(obj.BinInfoStats,A); obj.TableSettings = A(Ind(Ind>0)); % Keep same order in obj.BinInfoStats end else if length(obj.TableSettings) == 1 && strcmpi(obj.TableSettings,Value) % All are deselected. The reported statistics are % "Good" and "Bad" only. obj.TableSettings = {}; else A = setdiff(obj.TableSettings,Value,'stable'); [~,Ind] = ismember(obj.BinInfoStats,A); obj.TableSettings = A(Ind(Ind>0)); % Keep same order in obj.BinInfoStats end end N = length(src.Name); Name = src.Name(1:N-length('checkbox')); [~,Ind] = ismember(Name,obj.BinInfoStats); if strcmpi(Name,'members') obj.MembersCheckbox = TabName; end obj.setBinInfo(); % Keep checkboxes on both tabs (when they exist) in sync if strcmpi(TabName,{'overview'}) if obj.isViewExpanded AltCheckbox = obj.CtrlsManual.TableButtons(Ind); else AltCheckbox = []; end else AltCheckbox = obj.CtrlsOverview.TableButtons(Ind); end obj.isTableViewChanged = true; AltCheckbox.Selected = src.Selected; obj.isTableViewChanged = false; end function obj = setBinInfo(obj,varargin) % Called by the method obj.setInitialAppLayout and % obj.selectAxes % It adds (or updates) a figure object containing the statistics % from the creditscorecard object's method, 'bininfo'. % When the selected predictor changes, this method is called % wherever the method obj.setMainFigureLayout is called. % varargin = {true,String'). The boolean 'true' is just a % placeholder to have an additional input argument. It is when % this method is called after initialization Predictor = obj.SelectedPredictors{1}; if (nargin > 1) if strcmpi(varargin{1},'initialize') hFig = figure('Menubar','None','IntegerHandle','Off',... 'HandleVisibility','Off','Name',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinInfo',Predictor),... 'Visible','Off','CloseRequestFcn','','Resize','Off'); elseif strcmpi(varargin{1},'select') return; elseif ismember(varargin{1},{'overview','manual'}) hFig = obj.BinInfo; hFig.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinInfo',Predictor); delete(hFig.Children); end else hFig = obj.BinInfo; hFig.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:BinInfo',Predictor); delete(hFig.Children); end obj.computeBinInfo(Predictor); T = obj.StatsTable.(Predictor); RowNames = T.Bin; ColNames = T.Properties.VariableNames(2:end); data = table2cell(T(:,2:end)); [ok,Ind] = ismember('woe',lower(ColNames)); if ok data(end,Ind) = {''}; end [~,cg] = bininfo(obj.Scorecard,Predictor); % Display "Members" for categorical predictors if ismember(Predictor,obj.getCategoricalPredictors()) if nargin > 1 && strcmpi(varargin{1},{'initialize'}) [data,ColNames] = addMembersToBininfo(data,ColNames); else switch lower(obj.MembersCheckbox) case 'overview' if obj.CtrlsOverview.TableButtons(end).Selected [data,ColNames] = addMembersToBininfo(data,ColNames); end case 'manual' if obj.CtrlsManual.TableButtons(end).Selected [data,ColNames] = addMembersToBininfo(data,ColNames); end end end end uitable('Parent',hFig,'Data',data,'RowName',... RowNames,'ColumnName',ColNames,'Units','Normalized',... 'Position',[0.05 0.05 0.9 0.9],'Tag','bininfotable'); obj.BinInfo = hFig; function [data,ColNames] = addMembersToBininfo(data,ColNames) iGroupings = unique(cg.BinNumber); nGroups = length(iGroupings); Members = cell(nGroups,1); for i = 1 : nGroups Ind = (cg.BinNumber == iGroupings(i)); Members{i} = cg.Category(Ind); end for j = 1 : nGroups s = sprintf('%s,',Members{j}{:}); s(end) = ''; Members{j} = [' ' s]; end ColNames = [ColNames {risk.internal.app.utils.getMessage('risk:binningexplorerapp:MembersTable')}]; data = [data [Members;{''}]]; end end function obj = setPredictorInfo(obj,varargin) % Called by the method 'obj.setInitialAppLayout' % It adds a figure object containing the statistics from the % creditscorecard object's method, 'predictorinfo'. Predictor = obj.SelectedPredictors{1}; if (nargin > 1) if strcmpi(varargin{1},'initialize') hFig = figure('Menubar','None','IntegerHandle','Off',... 'HandleVisibility','Off','Visible','Off','Name',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorInfo',Predictor),... 'CloseRequestFcn','','Resize','Off'); elseif strcmpi(varargin{1},'select') return; end else hFig = obj.PredictorInfo; hFig.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorInfo',Predictor); delete(hFig.Children); end [~,T] = predictorinfo(obj.Scorecard,Predictor); RowNames = T.Properties.RowNames; ColNames = T.Properties.VariableNames; data = table2array(T); [~,cg] = bininfo(obj.Scorecard,Predictor); if (ismember(Predictor,obj.getCategoricalPredictors())) && ... (length(unique(cg.BinNumber)) < length(cg.Category)) ColNames = [ColNames {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Group')}]; data = [data cg.BinNumber]; end uitable('Parent',hFig,'Data',data,'Units','Normalized',... 'RowName',RowNames,'ColumnName',ColNames,'ColumnWidth',... {90 85},'Position',[0.05 0.05 0.9 0.9],'Tag','predictorinfotable'); obj.PredictorInfo = hFig; end function obj = computeBinInfo(obj,Predictor) % Called obj.setMainFigureLayout. This method adds a field % corresponding to Predictor in obj.StatsTable property for % display in the if isempty(obj.TableSettings) T = bininfo(obj.Scorecard,Predictor); obj.StatsTable.(Predictor) = T(:,... {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Bin'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Good'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Bad')}); else obj.StatsTable.(Predictor) = bininfo(obj.Scorecard,... Predictor,'Statistics',obj.TableSettings); end end %% App Layout / Figure and Callbacks function obj = setInitialAppLayout(obj) % ---> Step 1: Set the layout of the main figure and subplots obj.setInitialFigureLayout(); % ---> Step 2: Set the layout of the scorecard's BININFO table obj.setBinInfo('initialize'); % ---> Step 3: Set the layout of the predictor statistics obj.setPredictorInfo('initialize'); % Add the figures to the toolgroup obj.addFigure(obj.OverviewFigure) obj.addFigure(obj.BinInfo) obj.addFigure(obj.PredictorInfo) % Arrange figures (documents) grpname = obj.Name; md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; % One window initially to help with default layout md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(1,1)); % Main figure fills the entire first column md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(2,2)); md.setDocumentColumnSpan(grpname, 0, 0, 2); md.setDocumentRowHeights(grpname, [0.65, 0.35]); md.setDocumentColumnWidths(grpname, [0.7, 0.3]); % Assign documents to their tiles loc1 = com.mathworks.widgets.desk.DTLocation.create(0); loc2 = com.mathworks.widgets.desk.DTLocation.create(1); loc3 = com.mathworks.widgets.desk.DTLocation.create(2); md.setClientLocation(obj.OverviewFigure.Name,grpname,loc1); md.setClientLocation(obj.BinInfo.Name,grpname,loc2); md.setClientLocation(obj.PredictorInfo.Name,grpname,loc3); % Remove the "x" for closing Prop = com.mathworks.widgets.desk.DTClientProperty.PERMIT_USER_CLOSE; state = java.lang.Boolean.FALSE; pause(0.5); md.getClient(obj.OverviewFigure.Name,grpname).putClientProperty(Prop, state); md.getClient(obj.BinInfo.Name,grpname).putClientProperty(Prop, state); md.getClient(obj.PredictorInfo.Name,grpname).putClientProperty(Prop, state); % Turn on the visibility obj.OverviewFigure.Visible = 'On'; obj.BinInfo.Visible = 'On'; obj.PredictorInfo.Visible = 'On'; % Set the main fgure as the current one figure(obj.OverviewFigure); % Enable the controls on the toolstrip obj.enableControls(true); % Need to re-adjust the legend position after docking % drawnow nocallbacks; obj.setLegend(); % #ok something happens here, at the end of the call to setLegend: it seems the ML interpreter interprets the "drawnow" call before it is executed. end function obj = setInitialFigureLayout(obj) % Called by obj.setInitialAppLayout. It creates the main % figure to hold all the subplots and divides the plot area % into a grid. Predictors = obj.Scorecard.PredictorVars; NumCols = 2; NumPreds = length(Predictors); NumRows = ceil(NumPreds / NumCols); BaseRows = 5; Offset = -2*floor(NumRows/BaseRows); x1 = 0.075; x2 = 0.525; w = 0.4; % Prevent the figure from being closed. hFig = figure('Menubar','None','IntegerHandle','Off',... 'HandleVisibility','Off','Name',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Overview'),'Visible',... 'off','CloseRequestFcn','','Resize','Off','Renderer','painters'); auxPanel = uipanel('Parent',hFig,'Position',[0 0 1 1],... 'BorderType','None'); obj.PlotPanel = uipanel('Parent',auxPanel,'Position',[0 Offset... 1 1-Offset],'BorderType','None'); PanelPosition0 = obj.PlotPanel.Position; jScrollBar = javaObjectEDT('javax.swing.JScrollBar'); jScrollBar.setOrientation(jScrollBar.VERTICAL); jScrollBar.setMaximum(100); javacomponent(jScrollBar,'East',hFig); mScrollBar = handle(jScrollBar); obj.ScrollBar = jScrollBar; mScrollBar.AdjustmentValueChangedCallback = ... {@(src,ev)obj.sliderCallback(src,ev,PanelPosition0)}; % Setup the status bar to display plotting information javaMethodEDT('setSharedStatusBar', obj.JFrame, obj.JStatusBar) Delta = 100/NumPreds; % Display the "loading data" message hfTemp = figure('IntegerHandle','Off','HandleVisibility','Off',... 'Name','Plotting predictors','Visible','Off'); obj.addFigure(hfTemp); grpname = obj.Name; md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(1,1)); loc = com.mathworks.widgets.desk.DTLocation.create(0); md.setClientLocation('Importing Data',grpname,loc); uicontrol('Parent',hfTemp,'Style','Text','Units',... 'Normalized','Position',[0.25 0.3 0.5 0.2],'String',... 'Please wait while plotting the data','FontSize',12); hfTemp.Visible = 'On'; hfTemp.Pointer = 'watch'; pause(1); for i = 1 : NumPreds if mod(i,2) == 0 xPos = x2; else xPos = x1; end Value = obj.JProgressBar.getValue()+Delta; obj.JProgressBar.setValue(Value); obj.JLabel.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelPlot',... Predictors{i},num2str(i),num2str(NumPreds))); hf = plotbins(obj.Scorecard,Predictors{i},obj.getPlotSettings{:}); hf.HandleVisibility = 'Off'; hf.IntegerHandle = 'Off'; hf.Visible = 'Off'; hax = risk.internal.app.utils.extractChildren(hf,'axes'); hl = risk.internal.app.utils.extractChildren(hf,'legend'); hb = risk.internal.app.utils.extractChildren(hax,'bar'); hL = risk.internal.app.utils.extractChildren(hax,'line'); set(hb,'PickableParts','none'); set(hL,'PickableParts','none'); hTemp = subplot(NumRows,NumCols,i,'Parent',obj.PlotPanel); Pos = hTemp.Position; Pos = [xPos Pos(2)+0.05 w Pos(4)]; delete(hTemp) % Put the WOE curve on top so it is not hidden behind the % histogram hax = flipAxesAndAssignTags(hax,Predictors{i}); set([hax.YLabel],'String',''); set(hax,'YTick',[]); set(hax,'Parent',obj.PlotPanel); set(hax,'Position',Pos); set(hax,'ButtonDownFcn',@obj.selectAxes); % Make the first predictor as selected if i == 1 X = xlim(hax(1)); Y = ylim(hax(1)); rectangle('Parent',hax(1),'Position',[X(1) Y(1) ... diff(X) diff(Y)],'EdgeColor','blue',... 'LineWidth',2,'Selected','On'); obj.SelectedPredictors = Predictors(1); obj.SelectedAxes = hax(1); end if i == NumPreds DefaultVisibility = get(groot,'DefaultFigureVisible'); set(groot,'DefaultFigureVisible','Off') hl.PickableParts = 'None'; obj.Legend = hl; obj.setLegend(); set(groot,'DefaultFigureVisible',DefaultVisibility) end obj.AllAxesHandles.(Predictors{i}) = hax; delete(hf) end delete(hfTemp) obj.JLabel.setText(''); obj.JStatusBar.remove(obj.JProgressBar); % F. Refresh of the ToolGroup is sometimes necessary javaMethodEDT('validate', obj.JFrame) javaMethodEDT('repaint', obj.JFrame) hFig.KeyPressFcn = @obj.figureKeyPress; hFig.KeyReleaseFcn = @obj.figureKeyRelease; hFig.WindowScrollWheelFcn = @obj.scrollMouseWheel; obj.OverviewFigure = hFig; end function obj = setMainFigureLayout(obj,varargin) % Called by obj.showPlots, obj.applyPlotView and obj.expandView. % It updates the plots when algorithm or plot settings are % changed. if obj.isViewExpanded notify(obj,'SelectedFigureTab'); else if (nargin > 1) if strcmpi(varargin{1},'predictortype') Predictors = obj.ModifiedPredictors; elseif strcmpi(varargin{1},'BinningAppAlgorithmSettings') Predictors = obj.SelectedPredictors; else Predictors = obj.PredictorVars; end else Predictors = obj.SelectedPredictors; end if obj.isWOEChanged || obj.isBinTextChanged obj.showHideTextOrWOE(Predictors); else obj.updatePlot(Predictors,varargin{:}); end end % No need to update Bin Info and Predictor Info if the call % was triggered when the Plot Options were changed if ~obj.isBinTextChanged && ~obj.isWOEChanged obj.setBinInfo(); obj.setPredictorInfo(); end end function obj = showHideTextOrWOE(obj,Predictors) % Show or hide the WOE curve and/or the bin count labels. % Called by 'obj.setMainFigureLayout' for i = 1 : length(Predictors) if obj.isWOEChanged obj.AllAxesHandles.(Predictors{i})(2).Children.Visible = obj.PlotSettings.WOE; end if obj.isBinTextChanged bi = bininfo(obj.Scorecard,Predictors{i},'Totals','Off'); FreqTable = bi{:,{'Good','Bad'}}; if isempty(obj.Scorecard.ResponseVar) Bool = false; else Bool = true; end % Create Bin labels BinLabels = internal.finance.binning.utils.FormatBinLabels(FreqTable,Bool,size(FreqTable,1),obj.PlotSettings.BinText); % Display Bin Labels XTagLoc = obj.AllAxesHandles.(Predictors{i})(1).XTick; YTagLoc = sum(FreqTable,2); % In case unbinned data are used, the size of XTick might be % unreliable. Need to check size of XTagLoc. hBar = risk.internal.app.utils.extractChildren(obj.AllAxesHandles.(Predictors{i})(1),'bar'); if numel(XTagLoc) ~= numel(YTagLoc) if any(isnan(hBar(1).YData)) XTagLoc = 1; else XTagLoc = hBar(1).XData'; end obj.AllAxesHandles.(Predictors{i})(1).XTick = XTagLoc; end delete(risk.internal.app.utils.extractChildren(obj.AllAxesHandles.(Predictors{i})(1),'text')); % drawnow; if ~isempty(BinLabels) if Bool obj.BinTextHandles.(Predictors{i}) = text(XTagLoc,... YTagLoc,strcat(BinLabels(:,1),BinLabels(:,2),... BinLabels(:,3)),'FontWeight','Bold','HorizontalAlignment',... 'Center','VerticalAlignment','Bottom','Parent',... obj.AllAxesHandles.(Predictors{i})(1)); % drawnow; else obj.BinTextHandles.(Predictors{i}) = text(XTagLoc,... YTagLoc,BinLabels,'FontWeight','Bold',... 'HorizontalAlignment','Center','VerticalAlignment',... 'Bottom','Parent',obj.AllAxesHandles.(Predictors{i})(1)); % drawnow; end end end end end function obj = updatePlot(obj,Predictors,varargin) % Auxiliary method, called by 'obj.setMainFigureLayout' and % 'obj.updateZoomedFigure' if obj.isPlotChanged NumPreds = length(Predictors); javaMethodEDT('setSharedStatusBar', obj.JFrame, obj.JStatusBar); obj.JProgressBar = javaObjectEDT('javax.swing.JProgressBar'); obj.JStatusBar.add(obj.JProgressBar); Delta = 100/NumPreds; FigureVisibility = get(groot,'DefaultFigureVisible'); set(groot,'DefaultFigureVisible','off'); for i = 1 : NumPreds if ~obj.isViewExpanded Value = obj.JProgressBar.getValue()+Delta; obj.JProgressBar.setValue(Value); obj.JLabel.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelPlot',... Predictors{i},num2str(i),num2str(NumPreds))); end HistAxes = obj.AllAxesHandles.(Predictors{i})(1); Pos = HistAxes.Position; delete(obj.AllAxesHandles.(Predictors{i})); hf = plotbins(obj.Scorecard,Predictors{i},... obj.getPlotSettings{:}); hf.HandleVisibility = 'Off'; hax = risk.internal.app.utils.extractChildren(hf,'axes'); hl = risk.internal.app.utils.extractChildren(hf,'legend'); hb = risk.internal.app.utils.extractChildren(hax,'bar'); hL = risk.internal.app.utils.extractChildren(hax,'line'); if length(hax) == 1 hax.YLabel.String = ''; hax.YTick = []; else set([hax.YLabel],'String',''); set(hax,'YTick',[]); hax = flipAxesAndAssignTags(hax,Predictors{i}); end set(hax,'Parent',obj.PlotPanel,'Position',Pos); hax(1).Box = 'On'; if i == NumPreds obj.Legend = hl; hl.PickableParts = 'None'; obj.setLegend(); end delete(hf) % Figure selection if ismember(Predictors{i},obj.SelectedPredictors) X = xlim(hax(1)); Y = ylim(hax(1)); rectangle('Parent',hax(1),'Position',[X(1) Y(1) ... diff(X) diff(Y)],'EdgeColor','blue',... 'LineWidth',2,'Selected','On'); if all(isvalid(obj.SelectedAxes)) obj.SelectedAxes = [obj.SelectedAxes; hax(1)]; else obj.SelectedAxes = hax(1); end end set(hb,'PickableParts','none'); set(hL,'PickableParts','none'); set(hax,'ButtonDownFcn',@obj.selectAxes); obj.AllAxesHandles.(Predictors{i}) = hax; end set(groot,'DefaultFigureVisible',FigureVisibility); obj.JLabel.setText(''); obj.JStatusBar.remove(obj.JProgressBar); javaMethodEDT('validate', obj.JFrame); javaMethodEDT('repaint', obj.JFrame); end end function obj = updateZoomedFigure(obj,varargin) % Listener to the event 'SelectedFigureTab'. Makes sure the % selected figure in the 'Overview' tab is the same as in the % 'Manual' tab. Called by 'obj.selectAxes' and % 'obj.setMainFigureLayout' FigureVisibility = get(groot,'DefaultFigureVisible'); set(groot,'DefaultFigureVisible','off'); Predictor = obj.SelectedPredictors{1}; delete(obj.ZoomedFigure.Children); hf = plotbins(obj.Scorecard,Predictor,obj.getPlotSettings{:}); hax = risk.internal.app.utils.extractChildren(hf,'Axes'); hax = flipAxesAndAssignTags(hax,Predictor); set(hax,'Parent',obj.ZoomedFigure); hax(1).Box = 'On'; obj.ZoomedFigure.WindowButtonDownFcn = @obj.selectBin; obj.ZoomedFigure.WindowButtonMotionFcn = @obj.showBinInformation; obj.ZoomedFigure.Tag = Predictor; obj.ZoomedFigure.Name = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ManualFigureName',Predictor); try % When the ZoomedFigure is deleted before closing the app. obj.ZoomedFigure.CloseRequestFcn = @obj.closeZoomedFigure; catch MException throw(MException); end obj.resetBinEdgesMembersAfterReplot(hax); delete(hf); if obj.isShiftApplied || obj.isSplitApplied || ... obj.isMergeApplied || obj.isPredictorChanged obj.ManualBinningSection.remove(obj.ManualBinningPanel); obj.ManualBinningSection.add(obj.LocalSplitMergeSection); end % Update the Predictor type string and the status of the % "Members" checkbox if ismember(Predictor,obj.getNumericPredictors()) obj.CtrlsManual.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); % obj.CtrlsManual.TableButtons(end).Selected = false; % % does not work % re: toolstrip is rebuild with default % "selection" on (default on checkbox) obj.CtrlsManual.TableButtons(end).Selected = false; obj.CtrlsManual.TableButtons(end).Enabled = false; elseif ismember(Predictor,obj.getOrdinalPredictors()) obj.CtrlsManual.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal'); obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal'); obj.CtrlsManual.TableButtons(end).Enabled = true; else obj.CtrlsManual.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); obj.CtrlsManual.TableButtons(end).Enabled = true; end % Update the Selected Predictor string in the toolstrip if length(obj.SelectedAxes) < 2 obj.CtrlsOverview.PredictorButtons(4).Text = Predictor; else obj.CtrlsOverview.PredictorButtons(4).Text = ''; end set(groot,'DefaultFigureVisible',FigureVisibility); if obj.isBinTextChanged || obj.isWOEChanged obj.showHideTextOrWOE(obj.PredictorVars); else obj.updatePlot({Predictor}); end end function obj = figureKeyPress(obj,src,ev) % Callback on the 'KeyPressFcn' of the main figure if strcmpi(ev.Key,'control') src.UserData = 'ctrlclickon'; end end function obj = figureKeyRelease(obj,src,ev) % Callback on the 'KeyReleaseFcn' of the main figure if strcmpi(ev.Key,'control') src.UserData = 'ctrlclickoff'; end end function obj = figureShiftPress(obj,src,ev) % Callback on the 'WindowKeyPressFcn' of the zoomed figure. % Called through 'obj.selectBin'. if strcmpi(ev.Key,'shift') src.UserData = 'shiftclickon'; end end function obj = figureShiftRelease(obj,src,ev) % Callback on the 'WindowKeyReleaseFcn' of the zoomed figure. % Called through 'obj.selectBin'. if strcmpi(ev.Key,'shift') src.UserData = 'shiftclickoff'; end end function obj = showPlots(obj,varargin) % Called by 'obj.setPredictorType' and 'obj.runAlgorithm' and % 'obj.savePredictorSettings'. It is also called by the class % BinningAppAlgorithmSettings, upon pressing the 'OK' button % on the settings window. % This method runs automatic binning and displays the updated % plots. % Get the algorithm name and options AlgoName = obj.Algorithm.AlgoName; AlgoOptions = obj.Algorithm.Settings; Predictors = obj.SelectedPredictors; if nargin > 1 if strcmpi(varargin{1},'BinningAppAlgorithmSettings') % The boolean 'isAlgoUICalled' helps reset the boolean % 'isPlotChanged' to false, when the user uses algorithm % settings in an expanded view, then returns to the % overview to update it. obj.isPlotChanged = true; obj.isAlgoUICalled = true; elseif strcmpi(varargin{1},'predictortype') % Case when called by obj.savePredictorSettings: % When some or all predictors have their types changed, % they need to be updated, not just the selected ones % in the figure area. Predictors = obj.ModifiedPredictors; end end % Update the binning try obj.Scorecard = autobinning(obj.Scorecard,Predictors,... 'Algorithm',AlgoName,'AlgorithmOptions',AlgoOptions); obj.isScorecardExported = false; catch MException msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidAlgoOptionsMsg',MException.message),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidAlgoOptionsTitle'),'warn','modal'); return; end % Plot or update the toolstrip figure(s) obj.setMainFigureLayout(varargin{:}); % Reset flag if obj.isAlgoUICalled obj.isAlgoUICalled = false; end end function obj = selectAxes(obj,varargin) % ButtonDownFcn callback function on the subplots axes. Toggles % the axes selection to 'On' or 'Off', through setting a border % around the axes. % This method is called by 'obj.setInitialAppLayout' persistent MouseClick if ~isempty(obj.OverviewFigure.UserData) && ... strcmpi(obj.OverviewFigure.UserData,'ctrlclickon') obj.CtrlsOverview.ExpandButton.Enabled = false; obj.selectMultipleAxes(varargin{:}); return; end src = varargin{1}; obj.SelectedAxes(1).Box = 'On'; structfun(@(c)delete(risk.internal.app.utils.extractChildren(c,... 'rectangle')),obj.AllAxesHandles,'UniformOutput',false); X = src.XLim; Y = src.YLim; rectangle('Parent',src,'Position',[X(1) Y(1) diff(X) diff(Y)],... 'EdgeColor','blue','LineWidth',2,'Selected','On'); Predictor = src.Tag; obj.PreviousPredictor = obj.SelectedPredictors(1); obj.SelectedPredictors = {Predictor}; obj.SelectedAxes = src; obj.CtrlsOverview.ExpandButton.Enabled = true; % Update the Predictor type string and members in bin info table if ismember(Predictor,obj.getCategoricalPredictors()) if isordinal(obj.Scorecard.Data.(Predictor)) obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal'); else obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); end obj.CtrlsOverview.TableButtons(end).Enabled = true; else obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); obj.CtrlsOverview.TableButtons(end).Enabled = false; end % Update the Selected Predictor in the Toolstrip obj.CtrlsOverview.PredictorButtons(4).Text = Predictor; % Update statistic information obj.setBinInfo(); obj.setPredictorInfo(); % Display predictor information on the status bar StringInfo = obj.getStringPredictorInfo(); obj.JStatusBar.setText(strjoin(StringInfo,' - ')); % If double click, zoom on the selected axes. if isempty(MouseClick) MouseClick = 1; pause(0.5) if MouseClick == 1 % Single mouse click MouseClick = []; end else % Double mouse click. Bring the obj.ZoomedFigure to the % foreground MouseClick = []; obj.expandView(); figure(obj.ZoomedFigure) % bring up the zoomed figure return; end % Update the 'obj.ZoomedFigure' if open if obj.isViewExpanded obj.isPredictorChanged = true; notify(obj,'SelectedFigureTab'); obj.isPredictorChanged = false; end end function obj = selectMultipleAxes(obj,varargin) % ButtonDownFcn callback function on the subplots axes, when % the user CTRL-clicks. This method is called by 'obj.selectAxes' src = varargin{1}; X = src.XLim; Y = src.YLim; rectangle('Parent',src,'Position',[X(1) Y(1) diff(X) diff(Y)],... 'EdgeColor','blue','LineWidth',2,'Selected','On'); Predictor = src.Tag; obj.SelectedPredictors = unique([obj.SelectedPredictors ... Predictor],'stable'); obj.SelectedAxes = [obj.SelectedAxes ; src]; % Update the Predictor type string if all(ismember(obj.SelectedPredictors,obj.getCategoricalPredictors())) Bool = zeros(length(obj.SelectedPredictors),1); for i = 1 : length(obj.SelectedPredictors) Bool(i) = isordinal(obj.Scorecard.Data.(obj.SelectedPredictors{i})); end if all(Bool) obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal'); else obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); end elseif all(ismember(obj.SelectedPredictors,obj.getNumericPredictors())) obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); else obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MixedType'); end % Update the Selected Predictor name obj.CtrlsOverview.PredictorButtons(4).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:MultiSelect'); % Update statistic information obj.setBinInfo('select'); obj.setPredictorInfo('select'); % Update the 'obj.ZoomedFigure' if open if obj.isViewExpanded notify(obj,'SelectedFigureTab'); end end function obj = sliderCallback(obj,varargin) % Callback on the Slider UIcontrol on 'obj.PlotPanel'. Updates % the position of the panel. jPos = obj.ScrollBar.getValue(); OrigPosition = varargin{3}; obj.PlotPanel.Position(2) = OrigPosition(2) + ... (OrigPosition(4)-1)*(jPos/100); end function obj = scrollMouseWheel(obj,varargin) % Callback on the slider to update the position of the uipanel, % parent of all subplots. ev = varargin{2}; jPos = obj.ScrollBar.getValue(); if ev.VerticalScrollCount >0 setValue(obj.ScrollBar,min(jPos+5,100)); else setValue(obj.ScrollBar,max(jPos-5,0)); end end function obj = figureResizeCallback(obj,varargin) % Callback on the zoomed figures. It keeps the figure % size fixed if the user double-clicks on its header. src = varargin{1}; hax = risk.internal.app.utils.extractChildren(src,'axes'); [ok,Ind1] = ismember({hax.Units}','normalized'); if ~all(ok) if length(Ind1) == 2 Ind1 = find(Ind1); Ind2 = setdiff([1 2],Ind1); obj.ZoomedFigureAxesPosition = hax(Ind1).Position; hax(Ind2).Units = hax(Ind1).Units; hax(Ind2).Position = hax(Ind1).Position; else hax.Units = 'normalized'; hax.Position = obj.ZoomedFigureAxesPosition; end end end %% I/O Functions function obj = closeZoomedFigure(obj,varargin) % Deletes the zoomed figure used for manual binning, and sets % the object's property 'isViewExpanded' to false. try delete(obj.ZoomedFigure); obj.TabGroup = []; % Do we need this? Used this to avoid the issue with tab that disappears catch close all force; obj.approveClose; delete(obj); end obj.isViewExpanded = false; obj.OldBinNumber = []; obj.TooltipHandleBins = []; %obj.CtrlsOverview.ExpandButton.Enabled = true; end function CloseReqFcn(obj,varargin) % Close the app window src = varargin{1}; ev = varargin{2}; if strcmp(ev.EventData.EventType, 'CLOSING') && ... src.isClosingApprovalNeeded if isempty(obj.OverviewFigure) obj.approveClose; delete(obj); % c = onCleanup(@() rmappdata(groot,'BinningAppHandle')); % AppData = getappdata(groot); % if ismember('binningapphandle',lower(fieldnames(AppData))) % rmappdata(groot,'BinningAppHandle'); % end return; end if ~obj.isScorecardExported w = warning('off','MATLAB:callback:error'); c = onCleanup(@() warning(w)); try CloseSelection = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:CloseApp')); switch lower(CloseSelection) case 'yes' % Close and update the object's creditscorecard cleanupOnYes(); case 'no' % Close without updating the object's % creditscorecard if ~isempty(obj.ZoomedFigure) delete(obj.ZoomedFigure); end obj.OverviewFigure.CloseRequestFcn = 'closereq'; obj.BinInfo.CloseRequestFcn = 'closereq'; obj.PredictorInfo.CloseRequestFcn = 'closereq'; obj.approveClose; delete(obj); case 'cancel' obj.vetoClose; end catch close all force obj.approveClose; delete(obj); if ismember('binningapphandle',lower(fieldnames(getappdata(groot)))) rmappdata(groot,'BinningAppHandle') end end else cleanupOnYes(); end end function cleanupOnYes() try if isvalid(obj.ZoomedFigure) delete(obj.ZoomedFigure); end catch end obj.OverviewFigure.WindowButtonMotionFcn = ''; obj.OverviewFigure.CloseRequestFcn = 'closereq'; obj.BinInfo.CloseRequestFcn = 'closereq'; obj.PredictorInfo.CloseRequestFcn = 'closereq'; if ~obj.isScorecardExported obj.exportScorecard(); end obj.approveClose; delete(obj); end end function obj = loadData(obj,varargin) % If the user does not call Binningapp from the command line, % where they supply the creditscorecard object as input % argument, then they can launch the app and load the % data either form a table or an existing creditscorecard in the % workspace. This is a callback on the 'Load Data' button. % Disable the button to avoid double clicking if HG engine is % launched for the first time obj.CtrlsOverview.FileButtons.Enabled = false; w = 1100 ; h = 550; x0 = 400 ; y0 = 300; x1 = 2 ; y1 = 45; w1 = 0.25*w ; h1 = h-y1-1; x2 = x1+w1+x1 ; y2 = y1; w2 = 0.45*w ; h2 = h1; x3 = x2+w2+x1 ; y3 = y1; w3 = 0.295*w ; h3 = h1; hf = figure('Menubar','None','IntegerHandle','Off',... 'HandleVisibility','Off','Resize','Off','Name',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportDataText'),'Position',... [x0 y0 w h],'Visible','Off','CloseRequestFcn',... @obj.cancelLoadedData,'WindowStyle','modal'); movegui(hf,'center'); % Step 1: Data selection panel. The user must first click on the % data set to load before the table in Step 2 is displayed and % the algorithm radiobuttons are enabled. hc = obj.createDataSelectionPanel(hf,h,x1,y1,w1,h1); if isempty(hc) if obj.NumApps == 0 obj.NumApps = 1; else obj.NumApps = 0; end return; end % Step 2: Predictor type panel. Empty panel until the user % selects the dataset. hf = obj.createPredictorTypePanel(hf,h,x1,x2,y2,w2,h2); % Step 3: Initial algorithm panel. Disabled until the user % selected a dataset. obj.createInitialAlgorithmPanel(hf,h,x1,x3,y3,w3,h3); % Ok/Cancel Buttons uicontrol('Parent',hf,'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ImportDataText'),'Units',... 'pixels','Position',[x3 0.08*y3 w3/2-3 38],'Callback',... {@(src,ev)obj.saveLoadedData(src,ev,hf)},'Tag','importadatabtn'); uicontrol('Parent',hf,'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Cancel'),'Units',... 'pixels','Position',[x3+w3/2 0.08*y3 w3/2-3 38],'Callback',... @obj.cancelLoadedData,'Tag','cancelimportbtn'); % Set the default table selection to the first item in the list EventData = struct('Indices',[1 1],'Source',hc,'EventName','CellSelection'); obj.selectData(hc,EventData,hf); obj.CtrlsOverview.FileButtons.Enabled = true; end function obj = saveLoadedData(obj,src,~,hf) % Callback on the 'Ok' button in the window invoked by % pressing on the button 'Load Data'. Called by the method % 'obj.load Data' hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','predictorpanel'); ht = risk.internal.app.utils.extractChildren(hp,'uitable','Tag','predictortable'); data = obj.OriginalData; % Set predictors and response ht = obj.setPredictorResponse(ht,obj.ResponseChangedEvent,false); if isempty(ht) return; end Data = ht.Data; Vars = ht.RowName; % Remove excluded variables from the data Ind1 = strcmpi(Data(:,2),risk.internal.app.utils.getMessage('risk:binningexplorerapp:DoNotInclude')); if any(Ind1) Data(Ind1,:) = []; ExcludedPredictors = Vars(Ind1); if isa(data,'table') for i = 1 : length(ExcludedPredictors) data.(ExcludedPredictors{i}) = []; end else data.PredictorVars = setdiff(data.PredictorVars,... ExcludedPredictors,'stable'); end Vars(Ind1) = []; end if isempty(Data) || size(Data,1) < 2 msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataTitle'),'warn','modal'); return; end Ind2 = strcmpi(Data(:,2),risk.internal.app.utils.getMessage('risk:binningexplorerapp:Response')); Response = Vars(Ind2); % Processing the response data if isa(data,'table') if isempty(Response) msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:MissingResponseMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:MissingResponseTitle'),'warn','modal'); return; end RespData = data.(Response{:}); % Put the response at the end of the table and re-arrange the % data columns data.(Response{:}) = []; data(:,end+1) = table(RespData); data.Properties.VariableNames(end) = Response; end % Create creditscorecard to launch the app from if isa(data,'table') obj.Scorecard = creditscorecard(data,'ResponseVar',Response); else obj.Scorecard = data; end % Update the initial algorithm hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','algorithmpanel'); hb = risk.internal.app.utils.extractChildren(hp,'uibuttongroup'); AlgoName = hb.SelectedObject.String; obj.Algorithm.AlgoName = regexprep(AlgoName,' ',''); % Clean up previous session if obj.NumApps > 0 obj.cleanUpPreviousSession(); obj.NumApps = 0; else obj.NumApps = 1; end % Close the window delete(src.Parent) % Launch the app's main layout obj.setupBinningApp(); end function obj = cancelLoadedData(obj,src,~) if isa(src,'matlab.ui.Figure') delete(src) else delete(src.Parent); end if ~isempty(obj.OverviewFigure) obj.NumApps = 1; else obj.NumApps = 0; end % obj.NumApps = 0; end function obj = exportScorecard(obj,varargin) % Callback on the 'Export Scorecard' prompt = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportScorecard')}; title = risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportSection'); lines = 1; WkVars = evalin('base','whos'); def = getNameWithIncrement(WkVars); answer = inputdlg(prompt, title, lines, def); if ~isempty(answer) try if ismember(answer,{WkVars.name}') Choice = questdlg(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameMsg2',answer{:}),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameTitle'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Yes'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:No'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:No')); switch lower(Choice) case 'no' return; case 'yes' assignin('base',answer{:},obj.Scorecard); end else assignin('base',answer{:},obj.Scorecard); end catch MException if strcmpi(MException.identifier,'MATLAB:assigninInvalidVariable') FigureVisibility = get(groot,'DefaultFigureVisible'); set(groot,'DefaultFigureVisible','Off') hMsg = msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameMsg1'),... %'' risk.internal.app.utils.getMessage('risk:binningexplorerapp:InvalidExportNameTitle'),... 'warn','modal'); createUrlForNameFormat(); set(groot,'DefaultFigureVisible',FigureVisibility) else throw(MException) end end end obj.isScorecardExported = true; % Display on the status bar that the scorecard was saved in the % workspace obj.JStatusBar.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:ExportScorecardStatus',def{:})); function Name = getNameWithIncrement(W) Radical = 'sc'; Names = {W.name}'; Ind = ~cellfun('isempty',regexpi(Names,Radical)); Suffixe = cellfun(@(c) c(3:end),Names(Ind),'UniformOutput',false); Nums = cellfun(@(c) str2double(c),Suffixe,'UniformOutput',false); NumMat = cell2mat(Nums); IndNaN = isnan(NumMat); if isempty(NumMat) Name = {Radical}; elseif any(IndNaN) Inds = NumMat(~IndNaN); if isempty(Inds) Inds = 0; end Name = {[Radical num2str(1+max(Inds))]}; else Name = {[Radical num2str(1+max(NumMat))]}; end end function createUrlForNameFormat() hMsg.Position(4) = 100; hi = risk.internal.app.utils.extractChildren(hMsg,'Axes','Tag','IconAxes'); ha = setdiff(risk.internal.app.utils.extractChildren(hMsg,'Axes'),hi); ha.Children.Position(2) = 65; hi.Position(2) = 50; url = 'http://www.mathworks.com/help/matlab/matlab_prog/variable-names.html'; labelStr = ['<html>More info: <a href="">' sprintf('%s','MATLAB name conventions') '</a></html>']; jLabel = javaObjectEDT('javax.swing.JLabel', labelStr); [hjLabel,~] = javacomponent(jLabel, [50,40,250,35],hMsg); FigColor = hMsg.Color*255; Color = java.awt.Color(int16(FigColor(1)),int16(FigColor(2)),int16(FigColor(3))); hjLabel.setBackground(Color); hjLabel.setOpaque(true); % Modify the mouse cursor when hovering on the label hjLabel.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); % Set the label's tooltip hjLabel.setToolTipText(['Visit the: ' url ' website']); % Set the mouse-click callback set(hjLabel, 'MouseClickedCallback', @(h,e)web(['http://' url], '-browser')) end end function obj = cleanUpPreviousSession(obj) % Called by 'obj'saveLoadedData'. Cleans up all previous % properties of the App prior to loading new data. obj.OverviewFigure.CloseRequestFcn = 'closereq'; obj.BinInfo.CloseRequestFcn = 'closereq'; obj.PredictorInfo.CloseRequestFcn = 'closereq'; try delete(obj.OverviewFigure); delete(obj.BinInfo); delete(obj.PredictorInfo); delete(obj.TabGroup); delete(obj.ZoomedFigure); catch % Do nothing end obj.enableControls(false); obj.isAlgoUICalled = false; obj.AllAxesHandles = struct(); obj.BinTextHandles = struct(); obj.LeftEdgeValue = -Inf; obj.RightEdgeValue = Inf; obj.SelectedBins = []; obj.BinMembers = {}; obj.TooltipHandleBins = []; obj.isMergeApplied = false; obj.isShiftApplied = false; obj.isSplitApplied = false; obj.isDuplicateCutPoint = false; obj.isPredictorChanged = false; obj.OldNumBins = 2; obj.SelectedCategories = {}; obj.DefaultBinListing = {}; obj.OldBinNumber = []; obj.isWOEChanged = false; obj.isBinTextChanged = false; obj.PlotMenu = []; obj.TableMenu = []; obj.isInitPlot = true; obj.isInitTable = true; end function obj = setupBinningApp(obj) % This methods sets the default algorithm, algorithm settings % and other plot and statistics settings. obj.NumApps = 1; % Set up the default algorithm settings for future use obj.setDefaultAlgoSettings(); if isempty(obj.Algorithm.AlgoName) obj.Algorithm.AlgoName = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone'); end if ~strcmpi(obj.Algorithm.AlgoName,'nobinning') obj.Algorithm.Settings = obj.DefaultAlgoSettings.(obj.Algorithm.AlgoName); end % Step 1: Store predictors obj.PredictorVars = obj.Scorecard.PredictorVars(:); % A. Integrate the waitbar in the status bar. % Get the Java frame hosting the ToolGroup md = com.mathworks.mlservices.MatlabDesktopServices.getDesktop; jFrame = md.getFrameContainingGroup(obj.Name); % B. Create a new shared status bar StatusBar = javaObjectEDT('com.mathworks.mwswing.MJStatusBar'); javaMethodEDT('setSharedStatusBar', jFrame, StatusBar); % C: Status bar original text. StatusBar.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:StatusBar',... num2str(length(obj.PredictorVars)),... num2str(length(obj.getNumericPredictors())),... num2str(length(obj.getCategoricalPredictors())))); % D. Add Java components to the status bar Label = javaObjectEDT('javax.swing.JLabel',risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelBin','0')); StatusBar.add(Label); ProgressBar = javaObjectEDT('javax.swing.JProgressBar'); StatusBar.add(ProgressBar); % E. Update the progress bar status if ~strcmpi(obj.Algorithm.AlgoName,'nobinning') NumPreds = length(obj.PredictorVars); Delta = 100/NumPreds; % Display the "loading data" message hf = figure('IntegerHandle','Off','HandleVisibility','Off','Name','Binning data'); obj.addFigure(hf); grpname = obj.Name; md.setDocumentArrangement(grpname,md.TILED,java.awt.Dimension(1,1)); loc = com.mathworks.widgets.desk.DTLocation.create(0); md.setClientLocation('Importing Data',grpname,loc); uicontrol('Parent',hf,'Style','Text','Units',... 'Normalized','Position',[0.25 0.3 0.5 0.2],'String',... 'Please wait while binning the data','FontSize',12); hf.Pointer = 'watch'; pause(1); % For small dataset, in case we need to display the message. for i = 1 : NumPreds obj.Scorecard = autobinning(obj.Scorecard,... obj.PredictorVars{i},'Algorithm',... obj.Algorithm.AlgoName,'AlgorithmOptions',... obj.Algorithm.Settings); Value = ProgressBar.getValue()+Delta; ProgressBar.setValue(Value); Label.setText(risk.internal.app.utils.getMessage('risk:binningexplorerapp:JLabelBin',num2str(Value))); end delete(hf); ProgressBar.setValue(0); else % No need to bin, but need to set the default algorithm to % 'Monotone' for future use in the Algorithm Options window obj.Algorithm.AlgoName = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone'); obj.Algorithm.Settings = obj.DefaultAlgoSettings.(obj.Algorithm.AlgoName); end Label.setText(''); obj.JFrame = jFrame; obj.JStatusBar = StatusBar; obj.JProgressBar = ProgressBar; obj.JLabel = Label; % F. Refresh of the ToolGroup is sometimes necessary javaMethodEDT('validate', jFrame); javaMethodEDT('repaint', jFrame); % Step 2: Store figure, its default plot setting, the % creditscorecard object and table of default statistics. obj.SelectedPredictors = obj.Scorecard.PredictorVars(1); obj.PreviousPredictor = {}; obj.ModifiedPredictors = {}; obj.PlotSettings = struct('BinText','None','WOE','On'); obj.TableSettings = {'odds','woe','infovalue'}; obj.isPlotChanged = false; obj.isViewExpanded = false; obj.TooltipOn = true; obj.StatsTable = struct(obj.SelectedPredictors{:},... bininfo(obj.Scorecard,obj.SelectedPredictors{:},'Statistics',... obj.TableSettings)); if strcmpi(obj.Algorithm.AlgoName,'nobinning') obj.CtrlsOverview.AlgoButtons(1).Text = ... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo','Monotone'); else AlgoNameDisp = risk.internal.app.utils.formatNamesWithoutSpace({obj.Algorithm.AlgoName},'equal'); obj.CtrlsOverview.AlgoButtons(1).Text = ... risk.internal.app.utils.getMessage('risk:binningexplorerapp:ApplyAlgo',AlgoNameDisp{:}); end obj.CtrlsOverview.PlotButtons(1).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:None'); % Step 3: Update the predictor type button if ismember(obj.SelectedPredictors{1},obj.getOrdinalPredictors) obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal'); elseif ismember(obj.SelectedPredictors{1},obj.getCategoricalPredictors) obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); else obj.CtrlsOverview.PredictorButtons(2).Text = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); end % Step 4: Setup the predictor list for the predictors obj.CtrlsOverview.PredictorButtons(4).Text = obj.SelectedPredictors{1}; obj.CtrlsOverview.PredictorButtons(4).Popup = obj.setPredictorList('text_only'); obj.ActionListeners.PredSelection = ... addlistener(obj.CtrlsOverview.PredictorButtons(4).Popup,... 'ListItemSelected',@obj.selectAxesWrapper); % Step 5: Plot all predictors on the main figure obj.setInitialAppLayout(); % Step 6 obj.isInitPlot = false; obj.isInitTable = false; % Step 7: Update state of the "Member"s check box if ismember(obj.SelectedPredictors{1},obj.getCategoricalPredictors()) obj.CtrlsOverview.TableButtons(end).Enabled = true; obj.CtrlsOverview.TableButtons(end).Selected = true; else obj.CtrlsOverview.TableButtons(end).Enabled = false; obj.CtrlsOverview.TableButtons(end).Selected = false; end end function obj = enableControls(obj,bool) % Called by the constructor and by 'obj.setupBinningApp'. % If 'bool' is false, then all controls on the toolstrip are % disabled. If 'true', they are enabled. Fields = setdiff(fieldnames(obj.CtrlsOverview),'FileButtons'); for i = 1 : length(Fields) for j = 1 : length(obj.CtrlsOverview.(Fields{i})) obj.CtrlsOverview.(Fields{i})(j).Enabled = bool; end end end function hc = createDataSelectionPanel(obj,hf,h,x1,y1,w1,h1) % Method called by 'obj.loadData'. Sets the panel for Step 1: % Data selection. Wkspace = evalin('base','whos'); WkspaceClass = {Wkspace.class}; [ok,~] = ismember(lower(WkspaceClass),{'table','creditscorecard'}); WkspaceVars = {Wkspace(ok).name}; if isempty(WkspaceVars) msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataSelectMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:DataSelectTitle'),... 'warn','modal'); delete(hf); hc = []; obj.NumApps = 0; obj.CtrlsOverview.FileButtons.Enabled = true; return; else hf.Visible = 'On'; end hp = uipanel('Parent',hf,'Units','pixels','Position',... [x1 y1 w1 h1],'Tag','datapanel'); ha = axes('Parent',hp,'Visible','off','Units','Normalized',... 'Position',[0 0 1 1]); text('Parent',ha,'Units','pixels','Position',[x1+1 0.87*h],... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:StepNum','1'),... 'FontWeight','bold','Fontsize',14); text('Parent',ha,'Units','pixels','Position',[x1+1 0.82*h],... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step1Text')); hc = uitable('Parent',hp,'Units','Normalized','Position',... [0.03 0.05 0.95 0.82],'Data',[WkspaceVars' ... {Wkspace(ok).class}'],'RowName',[],'ColumnName',... {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Variable') ... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Class')},'ColumnWidth',... {120 134},'Tag','datatable','BackgroundColor',[1 1 1],... 'CellSelectionCallback',@(src,ev)obj.selectData(src,ev,hf)); end function obj = selectData(obj,src,ev,hf) % Callback on the listbox for data selection. Called by % 'obj.createDataSelectionPanel'. Ind = ev.Indices(:,1); if (length(Ind) > 1) || isempty(Ind) Ind = 1; end VarNames = src.Data(:,1); DataName = VarNames(Ind); data = evalin('base',DataName{:}); % Display the predictor table hf = obj.createPredictorTable(hf,data); % Enable the radio buttons hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','algorithmpanel'); hb = risk.internal.app.utils.extractChildren(hp,'uibuttongroup'); arrayfun(@(c)set(c,'Enable','On'),risk.internal.app.utils.extractChildren(hb,... 'uicontrol','Style','radiobutton'),'UniformOutput',false); obj.OriginalData = data; % If the loaded data is a table, set 'Monotone' as the default % algorithm. If the data is a creditscorecard object, set 'No % Binning' as the default. if isa(obj.OriginalData,'table') hb.SelectedObject = obj.MonotoneRadioBtn; else hb.SelectedObject = obj.NoBinningRadioBtn; end % Reset the event for changed response obj.ResponseChangedEvent = []; % Update the title for "Step 2" hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','predictorpanel'); ha = risk.internal.app.utils.extractChildren(hp,'Axes'); ht = risk.internal.app.utils.extractChildren(ha,'Text'); set(ht,'Interpreter','None'); Ind = regexpi('step 2',{ht.String}'); Ind = cellfun('isempty',Ind); ht(Ind).String = [risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step2Text') ... ' for "' DataName{:} '".']; end function hf = createPredictorTypePanel(~,hf,h,x1,x2,y2,w2,h2) % Method called by 'obj.loadData'. Sets the panel for Step 2: % Setting the predictor type (numeric, categorical, ordinal) % Create uipanel, uitable and axes hp = uipanel('Parent',hf,'Units','pixels','Position',... [x2 y2 w2 h2],'Tag','predictorpanel'); ha = axes('Parent',hp,'Visible','off','Units','Normalized',... 'Position',[0 0 1 1]); text('Parent',ha,'Units','pixels','Position',[x1+1 0.87*h],... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:StepNum','2'),... 'FontWeight','bold','FontSize',14); text('Parent',ha,'Units','pixels','Position',[x1+1 0.82*h],... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step2Text')); end function hf = createPredictorTable(obj,hf,data) % Create a uitable to set the predictor type and determine % which is predictor which is response. if isa(data,'table') Predictors = data.Properties.VariableNames; isNumericPreds = cellfun(@(c)isnumeric(data.(c)),Predictors'); isOrdinalPreds = getOrdinalPredictorsFromData(data,Predictors); DataClass = cellfun(@(c) class(data.(c)),Predictors','UniformOutput',false); else Predictors = [data.PredictorVars';data.ResponseVar]; isNumericPreds = ismember(Predictors,data.NumericPredictors'); isOrdinalPreds = getOrdinalPredictorsFromData(data.Data,Predictors); DataClass = cellfun(@(c) class(data.Data.(c)),Predictors','UniformOutput',false); end Ind = strcmpi(DataClass,'logical'); isNumericPreds(Ind) = true; NumPreds = length(Predictors); PredType = cell(NumPreds,1); PredType(isNumericPreds(:)) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric')}; PredType(~isNumericPreds(:)) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical')}; PredType(isOrdinalPreds(:)) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Ordinal')}; if isa(data,'creditscorecard') if isnumeric(data.Data.(data.ResponseVar)) PredType{end} = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Numeric'); else PredType{end} = risk.internal.app.utils.getMessage('risk:binningexplorerapp:Categorical'); end end % Determining if the variable is a predictor or a response VarType = [repmat({risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor')},NumPreds-1,1);... {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Response')}]; Data = [PredType VarType]; if isa(data,'creditscorecard') VarTypeColFmt = []; VarTypeColEdit = false; else VarTypeColFmt = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:Response'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:DoNotInclude')}; VarTypeColEdit = true; end hp = risk.internal.app.utils.extractChildren(hf,'uipanel','Tag','predictorpanel'); delete(risk.internal.app.utils.extractChildren(hp,'uitable','Tag','predictortable')); uitable('Parent',hp,'Data',Data,'Units','Normalized',... 'Position',[0.05 0.05 0.9 0.8],'RowName',Predictors,... 'ColumnWidth',{115 110},'ColumnName',... {risk.internal.app.utils.getMessage('risk:binningexplorerapp:PredictorType'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:VariableType')},... 'ColumnFormat',{[],VarTypeColFmt},'ColumnEditable',[false VarTypeColEdit],'Tag','predictortable',... 'CellEditCallback',@(src,ev)obj.setPredictorResponse(src,ev,true)); end function hb = createInitialAlgorithmPanel(obj,hf,h,x1,x3,y3,w3,h3) % Method called by 'obj.loadData'. Sets the panel for Step 3: % Data selection. hp = uipanel('Parent',hf,'Units','pixels','Position',... [x3 y3 w3 h3],'Tag','algorithmpanel'); ha = axes('Parent',hp,'Visible','off','Units','Normalized',... 'Position',[0 0 1 1]); text('Parent',ha,'Units','pixels','Position',[x1+1 0.87*h],... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:StepNum','3'),... 'FontWeight','bold','FontSize',14); text('Parent',ha,'Units','pixels','Position',[x1+1 0.82*h],... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Step3Text')); hb = uibuttongroup('Parent',hp,'Visible','on','Units','Normalized',... 'Position',[0.05 0 0.9 0.85],'BorderType','None'); obj.MonotoneRadioBtn = uicontrol(hb,'Style','radiobutton',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:Monotone'),... 'FontWeight','bold','Units','Normalized','Position',... [0.05 0.95 0.8 0.05],'Enable','Off','Tag','monotoneradiobtn'); uicontrol('Parent',hp,'Style','Text','Units','Normalized',... 'Position',[0.15 0.695 0.75 0.1],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:MonotoneDescription'),... 'HorizontalAlignment','left'); uicontrol(hb,'Style','radiobutton','String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:EF'),'FontWeight','bold',... 'Units','Normalized','Position',[0.05 0.73 0.8 0.05],... 'Enable','Off','Tag','efradiobtn'); uicontrol('Parent',hp,'Style','Text','Units','Normalized',... 'Position',[0.15 0.44 0.75 0.17],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:EFDescription'),... 'HorizontalAlignment','left'); uicontrol(hb,'Style','radiobutton','String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:EW'),'FontWeight','bold',... 'Units','Normalized','Position',[0.05 0.43 0.8 0.05],... 'Enable','Off','Tag','ewradiobtn'); uicontrol('Parent',hp,'Style','Text','Units','Normalized',... 'Position',[0.15 0.212 0.75 0.14],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:EWDescription'),... 'HorizontalAlignment','left'); obj.NoBinningRadioBtn = uicontrol(hb,'Style','radiobutton',... 'String',risk.internal.app.utils.getMessage('risk:binningexplorerapp:NoBinning'),... 'FontWeight','bold','Units','Normalized','Position',... [0.05 0.155 0.8 0.05],'Enable','Off','Tag','nobinningradiobtn'); uicontrol('Parent',hp,'Style','Text','Units','Normalized',... 'Position',[0.15 0.02 0.75 0.11],'String',... risk.internal.app.utils.getMessage('risk:binningexplorerapp:NoBinningDescription'),... 'HorizontalAlignment','left'); end function ht = setPredictorResponse(obj,src,ev,bool) % Callback on the CellEditCallback for the uitable. This % method makes sure to exclude variables marked as 'Do not % include' and only one variable can be set as a response. if bool obj.ResponseChangedEvent = ev; end Data = src.Data; Ind2 = strcmpi('response',Data(:,2)); rInd = false(length(Data),1); if isempty(ev) rInd(end) = true; else rInd(ev.Indices(1)) = true; end if size(Data,1) < 2 msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:LoadDataTitle'),'warn','modal'); ht = []; return; end % If the user sets one variable to 'response', then set all % others, except those excluded, to 'predictor'. if (~isempty(ev) && strcmpi(ev.NewData,'response')) || isempty(ev) % Validate that the response data is binary if isa(obj.OriginalData,'table') RespData = obj.OriginalData.(src.RowName{rInd}); else RespData = obj.OriginalData.Data.(src.RowName{rInd}); end if iscell(RespData) RespVal = unique(categorical(RespData)); else RespVal = unique(RespData); end RespVal(isnan(double(RespVal))) = []; if length(RespVal) ~= 2 msgbox(risk.internal.app.utils.getMessage('risk:binningexplorerapp:SetResponseMsg'),... risk.internal.app.utils.getMessage('risk:binningexplorerapp:SetResponseTitle'),... 'warn','modal'); src.Data(rInd,2) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor')}; ht = []; return; else PredInd = xor(Ind2,rInd); Data(PredInd,2) = {risk.internal.app.utils.getMessage('risk:binningexplorerapp:Predictor')}; src.Data = Data; end end ht = src; end end %% methods (Access = protected, Hidden = true) function obj = setLegend(obj) % Arranges the legend position when the plot is updated. Called % by 'obj.UpdatePlots' and 'obj.setInitialFigureLayout'. obj.Legend.Parent = obj.PlotPanel; obj.Legend.Position(1) = (1-obj.Legend.Position(3))/2; obj.Legend.Position(2) = 0.985; drawnow; end end end %% Helper functions function icon = getIcon(filename) pathstr = fileparts(mfilename('fullpath')); fullfilename = fullfile(pathstr, 'icons', filename); icon = toolpack.component.Icon(fullfilename); end function hax = flipAxesAndAssignTags(hax,Predictor) % Called by 'obj.setInitialFigureLayout', 'obj.updatePlot' and % 'obj.updateZoomedFigure'. It makes sure the first axes if % the histogram and sets the tags for both axes. if find(cellfun(@(c)~isempty(c),regexpi({hax.Tag},'woe'))) == 1 hax = flipud(hax); hax(1).Tag = Predictor; hax(2).Tag = [Predictor 'WOE']; end end function isOrdinalPreds = getOrdinalPredictorsFromData(Data,Predictors) % Returns a boolean array to index the input 'Predictors' and retrieve % a list of ordinal predictors. 'Data' is a table object. NumPreds = length(Predictors); isOrdinalPreds = false(NumPreds,1); for i = 1 : NumPreds try if isordinal(Data.(Predictors{i})) isOrdinalPreds(i) = true; else isOrdinalPreds(i) = false; end catch isOrdinalPreds(i) = false; end end end