function hf = wedgeplot(RES,varargin) %wedgeplot generates the double wedge plot of a time series % %Link to the help function % % Required input arguments: % % RES : absolute scaled residuals. Matrix or structure. % Matrix of size T-by-(T-lshift) containing scaled residuals % (in absolute value) for all the T units of the original time % series monitored in steps lshift+1, lshift+2, ..., T-lshift, % where lshift+1 is the first tentative level shift position, % lshift +2 is the second level shift position, and so on. This % matrix can be created by funtion LTSts (Least Trimmed Squares % in time series). If RES is a structure, it must contain field: % RES.RES = matrix containing scaled residuals. % Data Types - double. % % Optional input arguments: % % transpose: option determining the posiiton of the index number or tentative % level shift. Boolean. If transpose is true (default) the x-axis % contains the tentative level shift position and the y-axis the % index number else if it is false the axes are interchanged. % When transpose is true, it is possible with option extradata to % add on a separate panel a subplot of the original time series % (and possibly the series of fitted values). See extradata % option for details. % Example - 'transpose',false % Data Types - Boolean % extradata : extra data to plot in a separate panel in association to the % wedge plot. Matrix. Matrix of size T-by-1 or T-by-p containing % the data which have to be plotted in the separate panel. % Generally extradata is a matrix of size T-by-2 containing the % original time series and the corresponding fitted values in % order to link the irregularities shown by the wedgeplot with % the original time series. % - If extradata is empty (default) the double wedge plot will be % shown in a single panel. % - If extradata is not empty a two panel plot will be created: % one will contain the double wedge plot and extradata will be % plot in the other panel. This options makes sense only if % transpose is true, that is if the x axis of the double wedge % plot contains the index number. % When option transpose is left by the user unspecified, the % default position of the extradata subplot is at the bottom. % Otherwise, the position of the two panels depends on the order % with which the user specifies the two options: if extradata is % specified first, the corresponding subplot will be at the top, % otherwse it will fall at the bottom. % Example - 'extradata', [y yhat] % Data Types - double % cmapname: color map. Character. Character which indicates the type of % colormmap to use in the wedge plot. The accepted values are % 'hot', 'autumn', 'spring', 'pink', 'summer', 'winter', 'gray'. % The default is 'hot'. % Example - 'cmapname','summer' % Data Types - Character % labls : label of the axis which contains the level shift position. % Character. Character containing the label to put on the axis % which contains the level shift position. This axis could be % either the horizontal or vertical depending on the option % transpose. The default label is 'Tentative level shift % position'. % Example - 'labls','Position of level shift' % Data Types - Character % labin : label of the axis which contains the index number. % Character. Character containing the label to put on the axis % which contains the index number of the units of the time % series. This axis could be either the horizontal or vertical % depending on the option transpose. The default label is 'Index % number'. % Example - 'labin','unit number' % Types - Character % titl : Title. String. A label for the title (default: 'Double wedge % plot'). % Example - 'titl','Plot with two wedges' % Data Types - char % FontSize: Font size of the labels. Scalar. Scalar which controls the font % size of the labels of the axes and of the labels inside the % plot. Default value is 12. % Example - 'FontSize',12 % Data Types - double % SizeAxesNum: Size of the numbers of the axis. Scalar. Scalar which controls % the size of the numbers of the axes. Default value is 12. % Example - 'SizeAxesNum',10 % Data Types - double % % Output: % % hf : handle to the figure. Graphics handle. Handle to the figure % which has just been created. % % See also: plot % % References: % % Rousseeuw, P.J., Perrotta D., Riani M., Hubert M. (2017), Robust % Monitoring of Many Time Series with Application to Fraud Detection, % submitted. % % % Copyright 2008-2017. % Written by FSDA team % % %Link to the help function % Last modified 19-Jun-2017 % Examples: %{ % Double wedge plot with simulated data with linear trend and level shift. % No seasonal component. n=45; a=1; b=0.8; sig=1; seq=(1:n)'; y=a+b*seq+sig*randn(n,1); y(round(n/2):end)=y(round(n/2):end)+10; % model with a quadratic trend, non seasonal and level shift model=struct; model.trend=2; model.seasonal=0; % Potential level shift position is investigated in positions: % t=10, t=11, ..., t=T-10. model.lshift=10; out=LTSts(y,'model',model); wedgeplot(out,'transpose',true,'extradata',[y out.yhat]) %} %{ % Example of double wedge plot in series with level shift. % Analysis of contaminated airline data. % Load the airline data. % 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 y = [112 115 145 171 196 204 242 284 315 340 360 417 % Jan 118 126 150 180 196 188 233 277 301 318 342 391 % Feb 132 141 178 193 236 235 267 317 356 362 406 419 % Mar 129 135 163 181 235 227 269 313 348 348 396 461 % Apr 121 125 172 183 229 234 270 318 355 363 420 472 % May 135 149 178 218 243 264 315 374 422 435 472 535 % Jun 148 170 199 230 264 302 364 413 465 491 548 622 % Jul 148 170 199 242 272 293 347 405 467 505 559 606 % Aug 136 158 184 209 237 259 312 355 404 404 463 508 % Sep 119 133 162 191 211 229 274 306 347 359 407 461 % Oct 104 114 146 172 180 203 237 271 305 310 362 390 % Nov 118 140 166 194 201 229 278 306 336 337 405 432 ]; % Dec % Source: % http://datamarket.com/data/list/?q=provider:tsdl y=(y(:)); % Add a level shift contamintion plus some outliers. y(68:end)=y(68:end)+1300; y(67)=y(67)-600; y(45)=y(45)-800; y(68:69)=y(68:69)+800; % Create structure specifying model model=struct; model.trend=2; % quadratic trend model.s=12; % monthly time series model.seasonal=204; % number of harmonics model.lshift=40; % position where to start monitoring level shift model.X=''; % Create structure lts specifying lts options lts=struct; lts.bestr=20; % number of best solutions to bring to full convergence % h = dimension of the h subset (75 per cent of the data, bdp=0.25) h=round(0.75*length(y)); [out, varargout]=LTSts(y,'model',model,'nsamp',500,... 'lts',lts,'h',h,'plots',0,'msg',0); % Create the double wedge plot. wedgeplot(out) %} %{ %% Example of double wedge plot in series with level shift with option transpose. % Analysis of contaminated airline data. % Load the airline data. % 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960. y = [112 115 145 171 196 204 242 284 315 340 360 417 % Jan 118 126 150 180 196 188 233 277 301 318 342 391 % Feb 132 141 178 193 236 235 267 317 356 362 406 419 % Mar 129 135 163 181 235 227 269 313 348 348 396 461 % Apr 121 125 172 183 229 234 270 318 355 363 420 472 % May 135 149 178 218 243 264 315 374 422 435 472 535 % Jun 148 170 199 230 264 302 364 413 465 491 548 622 % Jul 148 170 199 242 272 293 347 405 467 505 559 606 % Aug 136 158 184 209 237 259 312 355 404 404 463 508 % Sep 119 133 162 191 211 229 274 306 347 359 407 461 % Oct 104 114 146 172 180 203 237 271 305 310 362 390 % Nov 118 140 166 194 201 229 278 306 336 337 405 432 ]; % Dec % Source: % http://datamarket.com/data/list/?q=provider:tsdl y=(y(:)); % Add a level shift contamintion plus some outliers. y(50:55)=y(50:55)-300; y(68:end)=y(68:end)-700; y(70:75)=y(70:75)+300; y(90:90)=y(90:90)+300; % Create structure specifying model model=struct; model.trend=2; % quadratic trend model.s=12; % monthly time series model.seasonal=204; % number of harmonics model.lshift=40; % position where to start monitoring level shift model.X=''; % Create structure lts specifying lts options lts=struct; lts.bestr=20; % number of best solutions to bring to full convergence % h = dimension of the h subset (75 per cent of the data, bdp=0.25) [out, varargout]=LTSts(y,'model',model,'nsamp',500,... 'lts',lts,'plots',0,'msg',0); % Create the double wedge plot. % Remember to remove the last column of the matrix of the residuals % obtained for each level shift position if you want to avoid the % top orange band (just execute RES(:,64)=[] before line 258). wedgeplot(out,'transpose',true,'extradata',[y out.yhat]) %} %{ % Same double wedge plot as before, but with the time series at the top % subplot. This is obtained simply by specifying extradata before transpose. wedgeplot(out,'extradata',[y out.yhat],'transpose',true); %} %% Beginning of code options=struct('extradata',[],'cmapname','hot',... 'labls','Level shift position','labin','Index number',... 'titl','Double wedge plot',... 'FontSize',12,'SizeAxesNum',12,'transpose',true); UserOptions=varargin(1:2:length(varargin)); if ~isempty(UserOptions) % Check if number of supplied options is valid if length(varargin) ~= 2*length(UserOptions) error('FSDA:wedgeplot:WrongInputOpt',... 'Number of supplied options is invalid. Probably values for some parameters are missing.'); end % Check if user options are valid options chkoptions(options,UserOptions) % Write in structure 'options' the options chosen by the user for i=1:2:length(varargin) options.(varargin{i})=varargin{i+1}; end end [extradata, cmapname, labls, labin, titl, FontSize, SizeAxesNum, transpose] = deal(... options.extradata, options.cmapname, options.labls, options.labin, ... options.titl, options.FontSize, options.SizeAxesNum, options.transpose); %% Initialise key structures % Check if input is a structure if isstruct(RES) if isfield(RES,'posLS') posLS = RES.posLS; else posLS = []; end if isfield(RES,'RES') residuals = RES.residuals; else residuals = []; end if isfield(RES,'RES') RES=abs(RES.RES); else error('FSDA:wedgeplot:WrongInput','Input structure must contain a field named ''RES''') end else % Take absolute values of RES RES = abs(RES); residuals = []; posLS = []; end % ADcont2 data; to avoid orange band, execute RES(:,64)=[] [T, l] = size(RES); % LSH = vector of integers associated with tentative level shift positions lshift = (T-l)/2; LSH = (lshift+1):(T-lshift); %% colormap scmap = T*l; switch cmapname case 'hot' cmap = hot(scmap); case 'autumn' cmap = autumn(scmap); case 'spring' cmap = spring(scmap); case 'pink' cmap = pink(scmap); case 'summer' cmap = summer(scmap); case 'winter' cmap = winter(scmap); case 'gray' cmap = gray(scmap); otherwise error('FSDA:wedgeplot:WrongInputOpt','The colormap name supplied is invalid.'); end %Small values should go with light colors cmap = flipud(cmap); % add a section to the colormap to obtain a grey background (gray colormaps are % unaffected, of course) if ~strcmp(cmapname, 'gray') gray_levels = 50; gcmap = flipud(gray(gray_levels)); gray_levels = 10; gcmap = gcmap(1:gray_levels,:); cmap = [gcmap ; cmap]; end %% initialise figure with a colorbar hf = figure; colormap(cmap); %% colors that will be used for the wedge plot % The colors of the wedge plot will be proportional to the scaled residuals. % Very large values (> 50) are set to 50. Cres=RES; Cres(Cres>50)=50; % a linear sequence of colormap weights from the minimum to the maximum % residuals values (0 to 50, in practice). scres_lin = (min(Cres(:)):max(Cres(:)))+1; scres_lin(1) = 0; %Partition the residuals in three parts: 'large', 'medium' and 'small' thtmp=50; rlarge = find(Cres>thtmp); %#ok may be used in future releases, e.g. Cres(rlarge)=thtmp; rmedium = find(Cres>2.58 & Cres<=10); %#ok may be used in future releases rsmall = Cres<=2.58; %The colors are rescaled so that to map the elements of the colormap Cres = Cres ./ max(max(Cres)); Cres = Cres * size(cmap,1); Cres = round(Cres); %Colors that point to 0 must be moved to 1 Cres(Cres==0) = 1; % Control backgroud if ~strcmp(cmapname, 'gray') %associate the small residuals to a gray color %for no gray, set Cres(rsmall) to 2 %for faint gray, set Cres(rsmall) to floor(gray_levels/2) %for strong gray, set Cres(rsmall) to gray_levels Cres(rsmall) = 1; end % if 0 % %this has to be discussed % Cres(rmedium) = round(Cres(rmedium) *3); % end %% generates the double wedge plot % In MATLAB releases before R2012b properties of surface and colorbar % objects were different vlt15 = verLessThan('matlab', '7.15'); Cres = [Cres; nan(1,length(LSH))]; Cres = [Cres nan(T+1,1)]; if transpose == false % the surface of the wedgeplot surface(zeros(size(Cres)),Cres,... 'EdgeColor','none','Xdata',[LSH nan]','CDataMapping','direct'); % axes labels xlabel(labls,'Fontsize',FontSize); ylabel(labin,'Fontsize',FontSize); % Colorbar and properties of the surface axes. Note the -1 and +1 in % the Ylim settings, i.e. in min(LSH-1) and max(LSH+1); this is needed % because the line of the Box would be covered by the surface of the % wedgeplot, at least in the bottom part of the plot. % The if statement addresses properties in different MATLAB releases. if ~vlt15 set(gca,'Box','on','Boxstyle','full','LineWidth',1,... 'Xlim',[min(LSH-1), max(LSH+1)],'Fontsize',SizeAxesNum); colorbar('Ticks' , prctile(1:size(cmap,1),[1 20 40 60 80 100]),... 'TickLabels' , round(prctile(scres_lin , [1 20 40 60 80 100])*100)/100,... 'Fontsize',FontSize); else set(gca,'Box','on','LineWidth',1,... 'Xlim',[min(LSH-1), max(LSH+1)],'Fontsize',SizeAxesNum); colorbar('YTick' , prctile(1:size(cmap,1),[1 20 40 60 80 100]),... 'YTickLabels' , round(prctile(scres_lin , [1 20 40 60 80 100])*100)/100,... 'Fontsize',FontSize); end title(titl); else % figure formed by two panels: the wedge plot and the time series if ~isempty(extradata) trapos = find(strcmpi('transpose',UserOptions)); extpos = find(strcmpi('extradata',UserOptions)); if extpos > trapos dps = 2; wps = 1; else dps = 1; wps = 2; end % subplot hosting the wedgeplot A(wps) = subplot(2,1,wps); else xlabel(labin,'Fontsize',FontSize); end % the surface of the wedgeplot surface(zeros(size(Cres))',Cres',... 'EdgeColor','none','Ydata',[LSH nan]','CDataMapping','direct'); % axes labels % xlabel(labin,'Fontsize',FontSize); ylabel(labls,'FontSize',FontSize); % Colorbar and properties of the surface axes. Note the -1 and +1 in % the Ylim settings, i.e. in min(LSH-1) and max(LSH+1); this is needed % because the line of the Box would be covered by the surface of the % wedgeplot, at least in the bottom part of the plot. % The if statement addresses properties in different MATLAB releases. if ~vlt15 set(gca,'Box','on','BoxStyle','full','LineWidth',1,... 'Ylim',[min(LSH-1), max(LSH+1)],'Fontsize',SizeAxesNum); colorbar('eastoutside','Ticks' , prctile(1:size(cmap,1),[1 20 40 60 80 100]),... 'TickLabels' , round(prctile(scres_lin , [1 20 40 60 80 100])*100)/100,... 'Fontsize',FontSize-2); else set(gca,'Box','on','LineWidth',1,... 'Ylim',[min(LSH-1), max(LSH+1)],'Fontsize',SizeAxesNum); colorbar('eastoutside','YTick' , prctile(1:size(cmap,1),[1 20 40 60 80 100]),... 'YTickLabels' , round(prctile(scres_lin , [1 20 40 60 80 100])*100)/100,... 'Fontsize',FontSize-2); end % the subplots have to be rescaled for leaving space to the colorbar if ~isempty(extradata) mine = min(extradata(:)); maxe = max(extradata(:)); delta = (maxe-mine)*0.1; yaxlim = [mine - delta ; maxe + delta]; A(dps) = subplot(2,1,dps); hold('on'); % mark outliers with their severity if ~isempty(residuals) seq = 1:T; quant = sqrt(chi2inv(1-0.01,1)); boolean=abs(residuals)>quant; resboo=residuals(boolean); modres=resboo; th=8; modres(abs(resboo)>th)=th; %Rescale residuals in the interval [0 3] sizeout=3*(abs(modres)-quant)/(th-quant); outliers=seq(boolean); for i=1:length(sizeout) plot(seq(outliers(i)),extradata(outliers(i),1),'x','LineWidth',sizeout(i),'Color','r', 'MarkerFaceColor','k'); end end % plot the vertical line of the level shift position and the % associated label on the X axis if ~isempty(posLS) line(posLS*ones(2,1) , yaxlim , 'LineStyle' , ':' , 'LineWidth' , 1.5 , 'Color' , 'k'); text(posLS , yaxlim(1) , num2str(posLS) , 'HorizontalAlignment' , 'Center' , 'VerticalAlignment' , 'Top'); end % plot the time series dd = size(extradata,2); clr = 'bkgmcyr'; syb = {'-','--','-.',':','-','--','-.'}; for d=1:dd plot(extradata(:,d),'Color',clr(d),'LineStyle',syb{d},'LineWidth',1); end xlabel(A(2),labin,'FontSize',FontSize); if ~vlt15 set(gca,'FontSize',SizeAxesNum,'Ylim' , yaxlim,'Box','on','BoxStyle','full'); else set(gca,'FontSize',SizeAxesNum,'Ylim' , yaxlim,'Box','on'); end for i=1:2 pos=get(A(i), 'Position'); axes(A(i)) ; %#ok set(A(i), 'Position', [pos(1) pos(2) .6626 pos(4)]); end title(A(1),titl); box('on'); else title(titl); end end % get the current axes position and increases the bottom margin to ensure % that the x label is not cut off position = get(gca,'OuterPosition'); [left, bottom, width, height] = deal(position(1),position(2),position(3),position(4)); set(gca,'OuterPosition',[left 0.7*bottom width height]); end