Commit e5aeec49133ba19a381291ad6dcad1164d621c61
1 parent
8ca6bc2fe9
Exists in
master
use real part
Showing 1 changed file with 1 additions and 1 deletions Inline Diff
allan_cov.m
function [retval, s, errorb, tau] = allan_cov(data,tau,name,verbose) | 1 | 1 | function [retval, s, errorb, tau] = allan_cov(data,tau,name,verbose) | |
% ALLAN Compute the Allan deviation for a set of time-domain frequency data | 2 | 2 | % ALLAN Compute the Allan deviation for a set of time-domain frequency data | |
% [RETVAL, S, ERRORB, TAU] = ALLAN(DATA,TAU,NAME,VERBOSE) | 3 | 3 | % [RETVAL, S, ERRORB, TAU] = ALLAN(DATA,TAU,NAME,VERBOSE) | |
% | 4 | 4 | % | |
% Inputs: | 5 | 5 | % Inputs: | |
% DATA should be a structure and have the following fields: | 6 | 6 | % DATA should be a structure and have the following fields: | |
% DATA.freq or DATA.phase | 7 | 7 | % DATA.freq or DATA.phase | |
% A vector of fractional frequency measurements (df/f) in | 8 | 8 | % A vector of fractional frequency measurements (df/f) in | |
% DATA.freq *or* phase offset data (seconds) in DATA.phase . | 9 | 9 | % DATA.freq *or* phase offset data (seconds) in DATA.phase . | |
% If frequency data is not present, it will be generated by | 10 | 10 | % If frequency data is not present, it will be generated by | |
% differentiating the phase data. | 11 | 11 | % differentiating the phase data. | |
% If both fields are present, then DATA.freq will be used. | 12 | 12 | % If both fields are present, then DATA.freq will be used. | |
% Note: for general-purpose calculations of Allan deviation, | 13 | 13 | % Note: for general-purpose calculations of Allan deviation, | |
% (i.e. a two-sample variance) use DATA.freq . | 14 | 14 | % (i.e. a two-sample variance) use DATA.freq . | |
% | 15 | 15 | % | |
% DATA.rate or DATA.time | 16 | 16 | % DATA.rate or DATA.time | |
% The sampling rate in Hertz (DATA.rate) or a vector of | 17 | 17 | % The sampling rate in Hertz (DATA.rate) or a vector of | |
% timestamps for each measurement in seconds (DATA.time). | 18 | 18 | % timestamps for each measurement in seconds (DATA.time). | |
% DATA.rate is used if both fields are present. | 19 | 19 | % DATA.rate is used if both fields are present. | |
% If DATA.rate == 0, then the timestamps are used. | 20 | 20 | % If DATA.rate == 0, then the timestamps are used. | |
% | 21 | 21 | % | |
% DATA.units (optional) | 22 | 22 | % DATA.units (optional) | |
% The units for the data. If present, the string DATA.units | 23 | 23 | % The units for the data. If present, the string DATA.units | |
% is added to the plot y-axis label. | 24 | 24 | % is added to the plot y-axis label. | |
% | 25 | 25 | % | |
% TAU is an array of tau values for computing Allan deviation. | 26 | 26 | % TAU is an array of tau values for computing Allan deviation. | |
% TAU values must be divisible by 1/DATA.rate (data points cannot be | 27 | 27 | % TAU values must be divisible by 1/DATA.rate (data points cannot be | |
% grouped in fractional quantities!) and invalid values are ignored. | 28 | 28 | % grouped in fractional quantities!) and invalid values are ignored. | |
% Leave empty to use default values. | 29 | 29 | % Leave empty to use default values. | |
% NAME is an optional label that is added to the plot titles. | 30 | 30 | % NAME is an optional label that is added to the plot titles. | |
% VERBOSE sets the level of status messages: | 31 | 31 | % VERBOSE sets the level of status messages: | |
% 0 = silent & no data plots; | 32 | 32 | % 0 = silent & no data plots; | |
% 1 = status messages & minimum plots; | 33 | 33 | % 1 = status messages & minimum plots; | |
% 2 = all messages and plots (default) | 34 | 34 | % 2 = all messages and plots (default) | |
% | 35 | 35 | % | |
% Outputs: | 36 | 36 | % Outputs: | |
% RETVAL is the array of Allan deviation values at each TAU. | 37 | 37 | % RETVAL is the array of Allan deviation values at each TAU. | |
% S is an optional output of other statistical measures of the data (mean, std, etc). | 38 | 38 | % S is an optional output of other statistical measures of the data (mean, std, etc). | |
% ERRORB is an optional output containing the error estimates for a 1-sigma | 39 | 39 | % ERRORB is an optional output containing the error estimates for a 1-sigma | |
% confidence interval. These values are shown on the figure for each point. | 40 | 40 | % confidence interval. These values are shown on the figure for each point. | |
% TAU is an optional output containing the array of tau values used in the | 41 | 41 | % TAU is an optional output containing the array of tau values used in the | |
% calculation (which may be a truncated subset of the input or default values). | 42 | 42 | % calculation (which may be a truncated subset of the input or default values). | |
% | 43 | 43 | % | |
% Example: | 44 | 44 | % Example: | |
% | 45 | 45 | % | |
% To compute the Allan deviation for the data in the variable "lt": | 46 | 46 | % To compute the Allan deviation for the data in the variable "lt": | |
% >> lt | 47 | 47 | % >> lt | |
% lt = | 48 | 48 | % lt = | |
% freq: [1x86400 double] | 49 | 49 | % freq: [1x86400 double] | |
% rate: 0.5 | 50 | 50 | % rate: 0.5 | |
% | 51 | 51 | % | |
% Use: | 52 | 52 | % Use: | |
% | 53 | 53 | % | |
% >> ad = allan(lt,[2 10 100],'lt data',1); | 54 | 54 | % >> ad = allan(lt,[2 10 100],'lt data',1); | |
% | 55 | 55 | % | |
% The Allan deviation will be computed and plotted at tau = 2,10,100 seconds. | 56 | 56 | % The Allan deviation will be computed and plotted at tau = 2,10,100 seconds. | |
% 1-sigma confidence intervals will be indicated by vertical lines at each point. | 57 | 57 | % 1-sigma confidence intervals will be indicated by vertical lines at each point. | |
% You can also use the default settings, which are usually a good starting point: | 58 | 58 | % You can also use the default settings, which are usually a good starting point: | |
% | 59 | 59 | % | |
% >> ad = allan(lt); | 60 | 60 | % >> ad = allan(lt); | |
% | 61 | 61 | % | |
% | 62 | 62 | % | |
% Notes: | 63 | 63 | % Notes: | |
% This function calculates the standard Allan deviation (ADEV), *not* the | 64 | 64 | % This function calculates the standard Allan deviation (ADEV), *not* the | |
% overlapping ADEV. Use "allan_overlap.m" for overlapping ADEV. | 65 | 65 | % overlapping ADEV. Use "allan_overlap.m" for overlapping ADEV. | |
% The calculation is performed using fractional frequency data. If only | 66 | 66 | % The calculation is performed using fractional frequency data. If only | |
% phase data is provided, frequency data is generated by differentiating | 67 | 67 | % phase data is provided, frequency data is generated by differentiating | |
% the phase data. | 68 | 68 | % the phase data. | |
% No pre-processing of the data is performed, except to remove any | 69 | 69 | % No pre-processing of the data is performed, except to remove any | |
% initial offset (i.e., starting gap) in the time record. | 70 | 70 | % initial offset (i.e., starting gap) in the time record. | |
% For rate-based data, ADEV is computed only for tau values greater than the | 71 | 71 | % For rate-based data, ADEV is computed only for tau values greater than the | |
% minimum time between samples and less than the half the total time. For | 72 | 72 | % minimum time between samples and less than the half the total time. For | |
% time-stamped data, only tau values greater than the maximum gap between | 73 | 73 | % time-stamped data, only tau values greater than the maximum gap between | |
% samples and less than half the total time are used. | 74 | 74 | % samples and less than half the total time are used. | |
% The calculation for fixed sample rate data is *much* faster than for | 75 | 75 | % The calculation for fixed sample rate data is *much* faster than for | |
% time-stamp data. You may wish to run the rate-based calculation first, | 76 | 76 | % time-stamp data. You may wish to run the rate-based calculation first, | |
% then compare with time-stamp-based. Often the differences are insignificant. | 77 | 77 | % then compare with time-stamp-based. Often the differences are insignificant. | |
% To show the "tau bins" (y_k samples) on the data plot, set the variable | 78 | 78 | % To show the "tau bins" (y_k samples) on the data plot, set the variable | |
% TAUBIN to 1 (search for "#TAUBIN"). | 79 | 79 | % TAUBIN to 1 (search for "#TAUBIN"). | |
% You can choose between loglog and semilog plotting of results by | 80 | 80 | % You can choose between loglog and semilog plotting of results by | |
% commenting in/out the appropriate line. Search for "#PLOTLOG". | 81 | 81 | % commenting in/out the appropriate line. Search for "#PLOTLOG". | |
% I recommend installing "dsplot.m", which improves the performance of | 82 | 82 | % I recommend installing "dsplot.m", which improves the performance of | |
% plotting large data sets. Download from File Exchange, File ID: #15850. | 83 | 83 | % plotting large data sets. Download from File Exchange, File ID: #15850. | |
% allan.m will use dsplot.m if it is present on your MATLAB path. | 84 | 84 | % allan.m will use dsplot.m if it is present on your MATLAB path. | |
% This function has been validated using the test data from NBS Monograph | 85 | 85 | % This function has been validated using the test data from NBS Monograph | |
% 140, the 1000-point test data set given by Riley [1], and the example data | 86 | 86 | % 140, the 1000-point test data set given by Riley [1], and the example data | |
% given in IEEE standard 1139-1999, Annex C. | 87 | 87 | % given in IEEE standard 1139-1999, Annex C. | |
% The author welcomes other validation results, see contact info below. | 88 | 88 | % The author welcomes other validation results, see contact info below. | |
% | 89 | 89 | % | |
% For more information, see: | 90 | 90 | % For more information, see: | |
% [1] W. J. Riley, "The Calculation of Time Domain Frequency Stability," | 91 | 91 | % [1] W. J. Riley, "The Calculation of Time Domain Frequency Stability," | |
% Available on the web: | 92 | 92 | % Available on the web: | |
% http://www.ieee-uffc.org/frequency_control/teaching.asp?name=paper1ht | 93 | 93 | % http://www.ieee-uffc.org/frequency_control/teaching.asp?name=paper1ht | |
% | 94 | 94 | % | |
% | 95 | 95 | % | |
% M.A. Hopcroft | 96 | 96 | % M.A. Hopcroft | |
% mhopeng at gmail dot com | 97 | 97 | % mhopeng at gmail dot com | |
% | 98 | 98 | % | |
% I welcome your comments and feedback! | 99 | 99 | % I welcome your comments and feedback! | |
% | 100 | 100 | % | |
% MH Mar2014 | 101 | 101 | % MH Mar2014 | |
% v2.24 fix bug related to generating freq data from phase with timestamps | 102 | 102 | % v2.24 fix bug related to generating freq data from phase with timestamps | |
% (thanks to S. David-Grignot for finding the bug) | 103 | 103 | % (thanks to S. David-Grignot for finding the bug) | |
% MH Oct2010 | 104 | 104 | % MH Oct2010 | |
% v2.22 tau truncation to integer groups; tau sort | 105 | 105 | % v2.22 tau truncation to integer groups; tau sort | |
% plotting bugfix | 106 | 106 | % plotting bugfix | |
% v2.20 sychronize updates across allan, allan_overlap, allan_modified | 107 | 107 | % v2.20 sychronize updates across allan, allan_overlap, allan_modified | |
% v2.16 add TAU as output, fixed unusual error with dsplot v1.1 | 108 | 108 | % v2.16 add TAU as output, fixed unusual error with dsplot v1.1 | |
% v2.14 update plotting behaviour, default tau values | 109 | 109 | % v2.14 update plotting behaviour, default tau values | |
% | 110 | 110 | % | |
111 | 111 | |||
versionstr = 'allan v2.24'; | 112 | 112 | versionstr = 'allan v2.24'; | |
113 | 113 | |||
% MH Jun2010 | 114 | 114 | % MH Jun2010 | |
% v2.12 bugfix for rate data row/col orientation | 115 | 115 | % v2.12 bugfix for rate data row/col orientation | |
% add DATA.units for plotting | 116 | 116 | % add DATA.units for plotting | |
% use dsplot.m for plotting | 117 | 117 | % use dsplot.m for plotting | |
% | 118 | 118 | % | |
% MH MAR2010 | 119 | 119 | % MH MAR2010 | |
% v2.1 minor interface and bugfixes | 120 | 120 | % v2.1 minor interface and bugfixes | |
% update data consistency check | 121 | 121 | % update data consistency check | |
% | 122 | 122 | % | |
% MH FEB2010 | 123 | 123 | % MH FEB2010 | |
% v2.0 Consistent code behaviour for all "allan_x.m" functions: | 124 | 124 | % v2.0 Consistent code behaviour for all "allan_x.m" functions: | |
% accept phase data | 125 | 125 | % accept phase data | |
% verbose levels | 126 | 126 | % verbose levels | |
% | 127 | 127 | % | |
% | 128 | 128 | % | |
% MH JAN2010 | 129 | 129 | % MH JAN2010 | |
% v1.84 code cleanup | 130 | 130 | % v1.84 code cleanup | |
% v1.82 typos in comments and code cleanup | 131 | 131 | % v1.82 typos in comments and code cleanup | |
% tau bin plotting changed for performance improvement | 132 | 132 | % tau bin plotting changed for performance improvement | |
% v1.8 Performance improvements: | 133 | 133 | % v1.8 Performance improvements: | |
% vectorize code for rate data | 134 | 134 | % vectorize code for rate data | |
% logical indexing for irregular rate data | 135 | 135 | % logical indexing for irregular rate data | |
% MH APR2008 | 136 | 136 | % MH APR2008 | |
% v1.62 loglog plot option | 137 | 137 | % v1.62 loglog plot option | |
% v1.61 improve error handling, plotting | 138 | 138 | % v1.61 improve error handling, plotting | |
% fix bug in regular data calc for high-rate data | 139 | 139 | % fix bug in regular data calc for high-rate data | |
% fix bug in timestamp data calc for large starting gap | 140 | 140 | % fix bug in timestamp data calc for large starting gap | |
% (thanks to C. B. Ruiz for identifying these bugs) | 141 | 141 | % (thanks to C. B. Ruiz for identifying these bugs) | |
% uses timestamps for DATA.rate=0 | 142 | 142 | % uses timestamps for DATA.rate=0 | |
% progress indicator for large timestamp data processing | 143 | 143 | % progress indicator for large timestamp data processing | |
% MH JUN2007 | 144 | 144 | % MH JUN2007 | |
% v1.54 Improve data plotting and optional bin plotting | 145 | 145 | % v1.54 Improve data plotting and optional bin plotting | |
% MH FEB2007 | 146 | 146 | % MH FEB2007 | |
% v1.5 use difference from median for plotting | 147 | 147 | % v1.5 use difference from median for plotting | |
% added MAD calculation for outlier detection | 148 | 148 | % added MAD calculation for outlier detection | |
% MH JAN2007 | 149 | 149 | % MH JAN2007 | |
% v1.48 plotting typos fixes | 150 | 150 | % v1.48 plotting typos fixes | |
% MH DEC2006 | 151 | 151 | % MH DEC2006 | |
% v1.46 hack to plot error bars | 152 | 152 | % v1.46 hack to plot error bars | |
% v1.44 further validation (Riley 1000-pt) | 153 | 153 | % v1.44 further validation (Riley 1000-pt) | |
% plot mean and std | 154 | 154 | % plot mean and std | |
% MH NOV2006 | 155 | 155 | % MH NOV2006 | |
% v1.42 typo fix comments | 156 | 156 | % v1.42 typo fix comments | |
% v1.4 fix irregular rate algorithm | 157 | 157 | % v1.4 fix irregular rate algorithm | |
% irregular algorithm rejects tau less than max gap in time data | 158 | 158 | % irregular algorithm rejects tau less than max gap in time data | |
% validate both algorithms using test data from NBS Monograph 140 | 159 | 159 | % validate both algorithms using test data from NBS Monograph 140 | |
% v1.3 fix time calc if data.time not present | 160 | 160 | % v1.3 fix time calc if data.time not present | |
% add error bars (not possible due to bug in MATLAB R14SP3) | 161 | 161 | % add error bars (not possible due to bug in MATLAB R14SP3) | |
% remove offset calculation | 162 | 162 | % remove offset calculation | |
% v1.24 improve feedback | 163 | 163 | % v1.24 improve feedback | |
% MH SEP2006 | 164 | 164 | % MH SEP2006 | |
% v1.22 updated comments | 165 | 165 | % v1.22 updated comments | |
% v1.2 errors and warnings | 166 | 166 | % v1.2 errors and warnings | |
% v1.1 handle irregular interval data | 167 | 167 | % v1.1 handle irregular interval data | |
%#ok<*AGROW> | 168 | 168 | %#ok<*AGROW> | |
169 | 169 | |||
% defaults | 170 | 170 | % defaults | |
if nargin < 4, verbose=2; end | 171 | 171 | if nargin < 4, verbose=2; end | |
if nargin < 3, name=''; end | 172 | 172 | if nargin < 3, name=''; end | |
if nargin < 2 || isempty(tau), tau=2.^(-10:10); end | 173 | 173 | if nargin < 2 || isempty(tau), tau=2.^(-10:10); end | |
174 | 174 | |||
% plot "tau bins"? #TAUBIN | 175 | 175 | % plot "tau bins"? #TAUBIN | |
TAUBIN = 0; % set 0 or 1 % WARNING: this has a significant impact on performance | 176 | 176 | TAUBIN = 0; % set 0 or 1 % WARNING: this has a significant impact on performance | |
177 | 177 | |||
% Formatting for plots | 178 | 178 | % Formatting for plots | |
FontName = 'Arial'; | 179 | 179 | FontName = 'Arial'; | |
FontSize = 14; | 180 | 180 | FontSize = 14; | |
plotlinewidth=2; | 181 | 181 | plotlinewidth=2; | |
182 | 182 | |||
if verbose >= 1, fprintf(1,'allan: %s\n\n',versionstr); end | 183 | 183 | if verbose >= 1, fprintf(1,'allan: %s\n\n',versionstr); end | |
184 | 184 | |||
%% Data consistency checks | 185 | 185 | %% Data consistency checks | |
if ~(isfield(data,'phase') || isfield(data,'freq')) | 186 | 186 | if ~(isfield(data,'phase') || isfield(data,'freq')) | |
error('Either ''phase'' or ''freq'' must be present in DATA. See help file for details. [con0]'); | 187 | 187 | error('Either ''phase'' or ''freq'' must be present in DATA. See help file for details. [con0]'); | |
end | 188 | 188 | end | |
if isfield(data,'time') | 189 | 189 | if isfield(data,'time') | |
if isfield(data,'phase') && (length(data.phase) ~= length(data.time)) | 190 | 190 | if isfield(data,'phase') && (length(data.phase) ~= length(data.time)) | |
if isfield(data,'freq') && (length(data.freq) ~= length(data.time)) | 191 | 191 | if isfield(data,'freq') && (length(data.freq) ~= length(data.time)) | |
error('The time and freq vectors are not the same length. See help for details. [con2]'); | 192 | 192 | error('The time and freq vectors are not the same length. See help for details. [con2]'); | |
else | 193 | 193 | else | |
error('The time and phase vectors are not the same length. See help for details. [con1]'); | 194 | 194 | error('The time and phase vectors are not the same length. See help for details. [con1]'); | |
end | 195 | 195 | end | |
end | 196 | 196 | end | |
if isfield(data,'phase') && (any(isnan(data.phase)) || any(isinf(data.phase))) | 197 | 197 | if isfield(data,'phase') && (any(isnan(data.phase)) || any(isinf(data.phase))) | |
error('The phase vector contains invalid elements (NaN/Inf). [con3]'); | 198 | 198 | error('The phase vector contains invalid elements (NaN/Inf). [con3]'); | |
end | 199 | 199 | end | |
if isfield(data,'freq') && (any(isnan(data.freq)) || any(isinf(data.freq))) | 200 | 200 | if isfield(data,'freq') && (any(isnan(data.freq)) || any(isinf(data.freq))) | |
error('The freq vector contains invalid elements (NaN/Inf). [con4]'); | 201 | 201 | error('The freq vector contains invalid elements (NaN/Inf). [con4]'); | |
end | 202 | 202 | end | |
if isfield(data,'time') && (any(isnan(data.time)) || any(isinf(data.time))) | 203 | 203 | if isfield(data,'time') && (any(isnan(data.time)) || any(isinf(data.time))) | |
error('The time vector contains invalid elements (NaN/Inf). [con5]'); | 204 | 204 | error('The time vector contains invalid elements (NaN/Inf). [con5]'); | |
end | 205 | 205 | end | |
end | 206 | 206 | end | |
207 | 207 | |||
% sort tau vector | 208 | 208 | % sort tau vector | |
tau=sort(tau); | 209 | 209 | tau=sort(tau); | |
210 | 210 | |||
211 | 211 | |||
%% Basic statistical tests on the data set | 212 | 212 | %% Basic statistical tests on the data set | |
if ~isfield(data,'freq') | 213 | 213 | if ~isfield(data,'freq') | |
if isfield(data,'rate') && data.rate ~= 0 | 214 | 214 | if isfield(data,'rate') && data.rate ~= 0 | |
data.freq=diff(data.phase).*data.rate; | 215 | 215 | data.freq=diff(data.phase).*data.rate; | |
elseif isfield(data,'time') | 216 | 216 | elseif isfield(data,'time') | |
data.freq=diff(data.phase)./diff(data.time); | 217 | 217 | data.freq=diff(data.phase)./diff(data.time); | |
end | 218 | 218 | end | |
if verbose >= 1, fprintf(1,'allan: Fractional frequency data generated from phase data (M=%g).\n',length(data.freq)); end | 219 | 219 | if verbose >= 1, fprintf(1,'allan: Fractional frequency data generated from phase data (M=%g).\n',length(data.freq)); end | |
data.time(1)=[]; % make time stamps correspond to freq data | 220 | 220 | data.time(1)=[]; % make time stamps correspond to freq data | |
end | 221 | 221 | end | |
if size(data.freq,2) > size(data.freq,1), data.freq=data.freq'; end % ensure columns | 222 | 222 | if size(data.freq,2) > size(data.freq,1), data.freq=data.freq'; end % ensure columns | |
223 | 223 | |||
s.numpoints=length(data.freq); | 224 | 224 | s.numpoints=length(data.freq); | |
s.max=max(data.freq); | 225 | 225 | s.max=max(data.freq); | |
s.min=min(data.freq); | 226 | 226 | s.min=min(data.freq); | |
s.mean=mean(data.freq); | 227 | 227 | s.mean=mean(data.freq); | |
s.median=median(data.freq); | 228 | 228 | s.median=median(data.freq); | |
if isfield(data,'time') | 229 | 229 | if isfield(data,'time') | |
if size(data.time,2) > size(data.time,1), data.time=data.time'; end % ensure columns | 230 | 230 | if size(data.time,2) > size(data.time,1), data.time=data.time'; end % ensure columns | |
s.linear=polyfit(data.time(1:length(data.freq)),data.freq,1); | 231 | 231 | s.linear=polyfit(data.time(1:length(data.freq)),data.freq,1); | |
elseif isfield(data,'rate') && data.rate ~= 0; | 232 | 232 | elseif isfield(data,'rate') && data.rate ~= 0; | |
s.linear=polyfit((1/data.rate:1/data.rate:length(data.freq)/data.rate)',data.freq,1); | 233 | 233 | s.linear=polyfit((1/data.rate:1/data.rate:length(data.freq)/data.rate)',data.freq,1); | |
else | 234 | 234 | else | |
error('Either "time" or "rate" must be present in DATA. Type "help allan" for details. [err1]'); | 235 | 235 | error('Either "time" or "rate" must be present in DATA. Type "help allan" for details. [err1]'); | |
end | 236 | 236 | end | |
s.std=std(data.freq); | 237 | 237 | s.std=std(data.freq); | |
238 | 238 | |||
if verbose >= 2 | 239 | 239 | if verbose >= 2 | |
fprintf(1,'allan: input data statistics:\n'); | 240 | 240 | fprintf(1,'allan: input data statistics:\n'); | |
disp(s); | 241 | 241 | disp(s); | |
end | 242 | 242 | end | |
243 | 243 | |||
244 | 244 | |||
% center at median for plotting | 245 | 245 | % center at median for plotting | |
medianfreq=data.freq-s.median; | 246 | 246 | medianfreq=data.freq-s.median; | |
sm=[]; sme=[]; | 247 | 247 | sm=[]; sme=[]; | |
248 | 248 | |||
% Screen for outliers using 5x Median Absolute Deviation (MAD) criteria | 249 | 249 | % Screen for outliers using 5x Median Absolute Deviation (MAD) criteria | |
s.MAD = median(abs(medianfreq)/0.6745); | 250 | 250 | s.MAD = median(abs(medianfreq)/0.6745); | |
if verbose >= 2 | 251 | 251 | if verbose >= 2 | |
fprintf(1, 'allan: 5x MAD value for outlier detection: %g\n',5*s.MAD); | 252 | 252 | fprintf(1, 'allan: 5x MAD value for outlier detection: %g\n',5*s.MAD); | |
end | 253 | 253 | end | |
if verbose >= 1 && any(abs(medianfreq) > 5*s.MAD) | 254 | 254 | if verbose >= 1 && any(abs(medianfreq) > 5*s.MAD) | |
fprintf(1, 'allan: NOTE: There appear to be outliers in the frequency data. See plot.\n'); | 255 | 255 | fprintf(1, 'allan: NOTE: There appear to be outliers in the frequency data. See plot.\n'); | |
end | 256 | 256 | end | |
257 | 257 | |||
258 | 258 | |||
%%%% | 259 | 259 | %%%% | |
% There are two cases, either using timestamps or fixed sample rate: | 260 | 260 | % There are two cases, either using timestamps or fixed sample rate: | |
261 | 261 | |||
%% Fixed Sample Rate Data | 262 | 262 | %% Fixed Sample Rate Data | |
% If there is a regular interval between measurements, calculation is much | 263 | 263 | % If there is a regular interval between measurements, calculation is much | |
% easier/faster | 264 | 264 | % easier/faster | |
if isfield(data,'rate') && data.rate > 0 % if data rate was given | 265 | 265 | if isfield(data,'rate') && data.rate > 0 % if data rate was given | |
if verbose >= 1, fprintf(1, 'allan: regular data (%g data points @ %g Hz)\n',length(data.freq),data.rate); end | 266 | 266 | if verbose >= 1, fprintf(1, 'allan: regular data (%g data points @ %g Hz)\n',length(data.freq),data.rate); end | |
267 | 267 | |||
% string for plot title | 268 | 268 | % string for plot title | |
name=[name ' (' num2str(data.rate) ' Hz)']; | 269 | 269 | name=[name ' (' num2str(data.rate) ' Hz)']; | |
270 | 270 | |||
% what is the time interval between data points? | 271 | 271 | % what is the time interval between data points? | |
tmstep = 1/data.rate; | 272 | 272 | tmstep = 1/data.rate; | |
273 | 273 | |||
% Is there time data? Just for curiosity/plotting, does not impact calculation | 274 | 274 | % Is there time data? Just for curiosity/plotting, does not impact calculation | |
if isfield(data,'time') | 275 | 275 | if isfield(data,'time') | |
% adjust time data to remove any starting gap; first time step | 276 | 276 | % adjust time data to remove any starting gap; first time step | |
% should not be zero for comparison with freq data | 277 | 277 | % should not be zero for comparison with freq data | |
dtime=data.time-data.time(1)+mean(diff(data.time)); | 278 | 278 | dtime=data.time-data.time(1)+mean(diff(data.time)); | |
if verbose >= 2 | 279 | 279 | if verbose >= 2 | |
fprintf(1,'allan: End of timestamp data: %g sec.\n',dtime(end)); | 280 | 280 | fprintf(1,'allan: End of timestamp data: %g sec.\n',dtime(end)); | |
if (data.rate - 1/mean(diff(dtime))) > 1e-6 | 281 | 281 | if (data.rate - 1/mean(diff(dtime))) > 1e-6 | |
fprintf(1,'allan: NOTE: data.rate (%f Hz) does not match average timestamped sample rate (%f Hz)\n',data.rate,1/mean(diff(dtime))); | 282 | 282 | fprintf(1,'allan: NOTE: data.rate (%f Hz) does not match average timestamped sample rate (%f Hz)\n',data.rate,1/mean(diff(dtime))); | |
end | 283 | 283 | end | |
end | 284 | 284 | end | |
else | 285 | 285 | else | |
% create time axis data using rate (for plotting only) | 286 | 286 | % create time axis data using rate (for plotting only) | |
dtime=(tmstep:tmstep:length(data.freq)*tmstep)'; % column oriented | 287 | 287 | dtime=(tmstep:tmstep:length(data.freq)*tmstep)'; % column oriented | |
end | 288 | 288 | end | |
289 | 289 | |||
% check the range of tau values and truncate if necessary | 290 | 290 | % check the range of tau values and truncate if necessary | |
% find halfway point of time record | 291 | 291 | % find halfway point of time record | |
halftime = round(tmstep*length(data.freq)/2); | 292 | 292 | halftime = round(tmstep*length(data.freq)/2); | |
% truncate tau to appropriate values | 293 | 293 | % truncate tau to appropriate values | |
tau = tau(tau >= tmstep & tau <= halftime); | 294 | 294 | tau = tau(tau >= tmstep & tau <= halftime); | |
if verbose >= 2, fprintf(1, 'allan: allowable tau range: %g to %g sec. (1/rate to total_time/2)\n',tmstep,halftime); end | 295 | 295 | if verbose >= 2, fprintf(1, 'allan: allowable tau range: %g to %g sec. (1/rate to total_time/2)\n',tmstep,halftime); end | |
296 | 296 | |||
% save the freq data for the loop | 297 | 297 | % save the freq data for the loop | |
dfreq=data.freq; | 298 | 298 | dfreq=data.freq; | |
dfreq2=data.freq2; | 299 | 299 | dfreq2=data.freq2; | |
% find the number of data points in each tau group | 300 | 300 | % find the number of data points in each tau group | |
m = data.rate.*tau; | 301 | 301 | m = data.rate.*tau; | |
% only integer values allowed (no fractional groups of points) | 302 | 302 | % only integer values allowed (no fractional groups of points) | |
%tau = tau(m-round(m)<1e-8); % numerical precision issues (v2.1) | 303 | 303 | %tau = tau(m-round(m)<1e-8); % numerical precision issues (v2.1) | |
tau = tau(m==round(m)); % The round() test is only correct for values < 2^53 | 304 | 304 | tau = tau(m==round(m)); % The round() test is only correct for values < 2^53 | |
%m = m(m-round(m)<1e-8); % change to round(m) for integer test v2.22 | 305 | 305 | %m = m(m-round(m)<1e-8); % change to round(m) for integer test v2.22 | |
m = m(m==round(m)); | 306 | 306 | m = m(m==round(m)); | |
%m=round(m); | 307 | 307 | %m=round(m); | |
308 | 308 | |||
if verbose >= 1, fprintf(1,'allan: calculating Allan deviation...\n '); end | 309 | 309 | if verbose >= 1, fprintf(1,'allan: calculating Allan deviation...\n '); end | |
310 | 310 | |||
% calculate the Allan deviation for each value of tau | 311 | 311 | % calculate the Allan deviation for each value of tau | |
k=0; tic; | 312 | 312 | k=0; tic; | |
for i = tau | 313 | 313 | for i = tau | |
if verbose >= 2, fprintf(1,'%g ',i); end | 314 | 314 | if verbose >= 2, fprintf(1,'%g ',i); end | |
k=k+1; | 315 | 315 | k=k+1; | |
316 | 316 | |||
% truncate frequency set to an even multiple of this tau value | 317 | 317 | % truncate frequency set to an even multiple of this tau value | |
freq=dfreq(1:end-rem(length(dfreq),m(k))); | 318 | 318 | freq=dfreq(1:end-rem(length(dfreq),m(k))); | |
freq2=dfreq2(1:end-rem(length(dfreq2),m(k))); | 319 | 319 | freq2=dfreq2(1:end-rem(length(dfreq2),m(k))); | |
% group the data into tau-length groups or bins | 320 | 320 | % group the data into tau-length groups or bins | |
f = reshape(freq,m(k),[]); % Vectorize! | 321 | 321 | f = reshape(freq,m(k),[]); % Vectorize! | |
f2 = reshape(freq2,m(k),[]); % Vectorize! | 322 | 322 | f2 = reshape(freq2,m(k),[]); % Vectorize! | |
% find average in each "tau group", y_k (each colummn of f) | 323 | 323 | % find average in each "tau group", y_k (each colummn of f) | |
fa=mean(f,1); | 324 | 324 | fa=mean(f,1); | |
fa2=mean(f2,1); | 325 | 325 | fa2=mean(f2,1); | |
% first finite difference | 326 | 326 | % first finite difference | |
fd=diff(fa); | 327 | 327 | fd=diff(fa); | |
fd2=diff(fa2); | 328 | 328 | fd2=diff(fa2); | |
% calculate two-sample variance for this tau | 329 | 329 | % calculate two-sample variance for this tau | |
M=length(fa); | 330 | 330 | M=length(fa); | |
sm(k)=sqrt(0.5/(M-1)*(real(sum(fd.*fd2)))); | 331 | 331 | sm(k)=sqrt(0.5/(M-1)*abs(real(sum(fd.*fd2)))); | |
332 | 332 | |||
% estimate error bars | 333 | 333 | % estimate error bars | |
sme(k)=sm(k)/sqrt(M+1); | 334 | 334 | sme(k)=sm(k)/sqrt(M+1); | |
335 | 335 | |||
if TAUBIN == 1 | 336 | 336 | if TAUBIN == 1 | |
% save the binning points for plotting | 337 | 337 | % save the binning points for plotting | |
fs(k,1:length(freq)/m(k))=m(k):m(k):length(freq); fval{k}=mean(f,1); | 338 | 338 | fs(k,1:length(freq)/m(k))=m(k):m(k):length(freq); fval{k}=mean(f,1); | |
end | 339 | 339 | end | |
340 | 340 | |||
end % repeat for each value of tau | 341 | 341 | end % repeat for each value of tau | |
342 | 342 | |||
if verbose >= 2, fprintf(1,'\n'); end | 343 | 343 | if verbose >= 2, fprintf(1,'\n'); end | |
calctime=toc; if verbose >= 2, fprintf(1,'allan: Elapsed time for calculation: %e seconds\n',calctime); end | 344 | 344 | calctime=toc; if verbose >= 2, fprintf(1,'allan: Elapsed time for calculation: %e seconds\n',calctime); end | |
345 | 345 | |||
346 | 346 | |||
347 | 347 | |||
%% Irregular data (timestamp) | 348 | 348 | %% Irregular data (timestamp) | |
elseif isfield(data,'time') | 349 | 349 | elseif isfield(data,'time') | |
% the interval between measurements is irregular | 350 | 350 | % the interval between measurements is irregular | |
% so we must group the data by time | 351 | 351 | % so we must group the data by time | |
if verbose >= 1, fprintf(1, 'allan: irregular rate data (no fixed sample rate)\n'); end | 352 | 352 | if verbose >= 1, fprintf(1, 'allan: irregular rate data (no fixed sample rate)\n'); end | |
353 | 353 | |||
% string for plot title | 354 | 354 | % string for plot title | |
name=[name ' (timestamp)']; | 355 | 355 | name=[name ' (timestamp)']; | |
356 | 356 | |||
% adjust time to remove any initial offset or zero | 357 | 357 | % adjust time to remove any initial offset or zero | |
dtime=data.time-data.time(1)+mean(diff(data.time)); | 358 | 358 | dtime=data.time-data.time(1)+mean(diff(data.time)); | |
%dtime=data.time; | 359 | 359 | %dtime=data.time; | |
% where is the maximum gap in time record? | 360 | 360 | % where is the maximum gap in time record? | |
gap_pos=find(diff(dtime)==max(diff(dtime))); | 361 | 361 | gap_pos=find(diff(dtime)==max(diff(dtime))); | |
% what is average data spacing? | 362 | 362 | % what is average data spacing? | |
avg_gap = mean(diff(dtime)); | 363 | 363 | avg_gap = mean(diff(dtime)); | |
364 | 364 | |||
if verbose >= 2 | 365 | 365 | if verbose >= 2 | |
fprintf(1, 'allan: WARNING: irregular timestamp data (no fixed sample rate).\n'); | 366 | 366 | fprintf(1, 'allan: WARNING: irregular timestamp data (no fixed sample rate).\n'); | |
fprintf(1, ' Calculation time may be long and the results subject to interpretation.\n'); | 367 | 367 | fprintf(1, ' Calculation time may be long and the results subject to interpretation.\n'); | |
fprintf(1, ' You are advised to estimate using an average sample rate (%g Hz) instead of timestamps.\n',1/avg_gap); | 368 | 368 | fprintf(1, ' You are advised to estimate using an average sample rate (%g Hz) instead of timestamps.\n',1/avg_gap); | |
fprintf(1, ' Continue at your own risk! (press any key to continue)\n'); | 369 | 369 | fprintf(1, ' Continue at your own risk! (press any key to continue)\n'); | |
pause; | 370 | 370 | pause; | |
end | 371 | 371 | end | |
372 | 372 | |||
if verbose >= 1 | 373 | 373 | if verbose >= 1 | |
fprintf(1, 'allan: End of timestamp data: %g sec\n',dtime(end)); | 374 | 374 | fprintf(1, 'allan: End of timestamp data: %g sec\n',dtime(end)); | |
fprintf(1, ' Average rate: %g Hz (%g sec/measurement)\n',1/avg_gap,avg_gap); | 375 | 375 | fprintf(1, ' Average rate: %g Hz (%g sec/measurement)\n',1/avg_gap,avg_gap); | |
if max(diff(dtime)) ~= 1/mean(diff(dtime)) | 376 | 376 | if max(diff(dtime)) ~= 1/mean(diff(dtime)) | |
fprintf(1, ' Max. gap: %g sec at position %d\n',max(diff(dtime)),gap_pos(1)); | 377 | 377 | fprintf(1, ' Max. gap: %g sec at position %d\n',max(diff(dtime)),gap_pos(1)); | |
end | 378 | 378 | end | |
if max(diff(dtime)) > 5*avg_gap | 379 | 379 | if max(diff(dtime)) > 5*avg_gap | |
fprintf(1, ' WARNING: Max. gap in time record is suspiciously large (>5x the average interval).\n'); | 380 | 380 | fprintf(1, ' WARNING: Max. gap in time record is suspiciously large (>5x the average interval).\n'); | |
end | 381 | 381 | end | |
end | 382 | 382 | end | |
383 | 383 | |||
384 | 384 | |||
% find halfway point | 385 | 385 | % find halfway point | |
halftime = fix(dtime(end)/2); | 386 | 386 | halftime = fix(dtime(end)/2); | |
% truncate tau to appropriate values | 387 | 387 | % truncate tau to appropriate values | |
tau = tau(tau >= max(diff(dtime)) & tau <= halftime); | 388 | 388 | tau = tau(tau >= max(diff(dtime)) & tau <= halftime); | |
if isempty(tau) | 389 | 389 | if isempty(tau) | |
error('allan: ERROR: no appropriate tau values (> %g s, < %g s)\n',max(diff(dtime)),halftime); | 390 | 390 | error('allan: ERROR: no appropriate tau values (> %g s, < %g s)\n',max(diff(dtime)),halftime); | |
end | 391 | 391 | end | |
392 | 392 | |||
% save the freq data for the loop | 393 | 393 | % save the freq data for the loop | |
dfreq=data.freq; | 394 | 394 | dfreq=data.freq; | |
dtime=dtime(1:length(dfreq)); | 395 | 395 | dtime=dtime(1:length(dfreq)); | |
396 | 396 | |||
if verbose >= 1, fprintf(1,'allan: calculating Allan deviation...\n'); end | 397 | 397 | if verbose >= 1, fprintf(1,'allan: calculating Allan deviation...\n'); end | |
398 | 398 | |||
k=0; tic; | 399 | 399 | k=0; tic; | |
for i = tau | 400 | 400 | for i = tau | |
if verbose >= 2, fprintf(1,'%d ',i); end | 401 | 401 | if verbose >= 2, fprintf(1,'%d ',i); end | |
402 | 402 | |||
k=k+1; fa=[]; %f=[]; | 403 | 403 | k=k+1; fa=[]; %f=[]; | |
km=0; | 404 | 404 | km=0; | |
405 | 405 | |||
% truncate data set to an even multiple of this tau value | 406 | 406 | % truncate data set to an even multiple of this tau value | |
freq=dfreq(dtime <= dtime(end)-rem(dtime(end),i)); | 407 | 407 | freq=dfreq(dtime <= dtime(end)-rem(dtime(end),i)); | |
time=dtime(dtime <= dtime(end)-rem(dtime(end),i)); | 408 | 408 | time=dtime(dtime <= dtime(end)-rem(dtime(end),i)); | |
%freq=dfreq; | 409 | 409 | %freq=dfreq; | |
%time=dtime; | 410 | 410 | %time=dtime; | |
411 | 411 | |||
% break up the data into groups of tau length in sec | 412 | 412 | % break up the data into groups of tau length in sec | |
while i*km < time(end) | 413 | 413 | while i*km < time(end) | |
km=km+1; | 414 | 414 | km=km+1; | |
415 | 415 | |||
% progress bar | 416 | 416 | % progress bar | |
if verbose >= 2 | 417 | 417 | if verbose >= 2 | |
if rem(km,100)==0, fprintf(1,'.'); end | 418 | 418 | if rem(km,100)==0, fprintf(1,'.'); end | |
if rem(km,1000)==0, fprintf(1,'%g/%g\n',km,round(time(end)/i)); end | 419 | 419 | if rem(km,1000)==0, fprintf(1,'%g/%g\n',km,round(time(end)/i)); end | |
end | 420 | 420 | end | |
421 | 421 | |||
f = freq(i*(km-1) < time & time <= i*km); | 422 | 422 | f = freq(i*(km-1) < time & time <= i*km); | |
f = f(~isnan(f)); % make sure values are valid | 423 | 423 | f = f(~isnan(f)); % make sure values are valid | |
424 | 424 | |||
if ~isempty(f) | 425 | 425 | if ~isempty(f) | |
fa(km)=mean(f); | 426 | 426 | fa(km)=mean(f); | |
else | 427 | 427 | else | |
fa(km)=0; | 428 | 428 | fa(km)=0; | |
end | 429 | 429 | end | |
430 | 430 | |||
if TAUBIN == 1 % WARNING: this has a significant impact on performance | 431 | 431 | if TAUBIN == 1 % WARNING: this has a significant impact on performance | |
% save the binning points for plotting | 432 | 432 | % save the binning points for plotting | |
%if find(time <= i*km) > 0 | 433 | 433 | %if find(time <= i*km) > 0 | |
fs(k,km)=max(time(time <= i*km)); | 434 | 434 | fs(k,km)=max(time(time <= i*km)); | |
%else | 435 | 435 | %else | |
if isempty(fs(k,km)) | 436 | 436 | if isempty(fs(k,km)) | |
fs(k,km)=0; | 437 | 437 | fs(k,km)=0; | |
end | 438 | 438 | end | |
fval{k}=fa; | 439 | 439 | fval{k}=fa; | |
end % save tau bin plot points | 440 | 440 | end % save tau bin plot points | |
441 | 441 | |||
end | 442 | 442 | end | |
443 | 443 | |||
if verbose >= 2, fprintf(1,'\n'); end | 444 | 444 | if verbose >= 2, fprintf(1,'\n'); end | |
445 | 445 | |||
% first finite difference of the averaged results | 446 | 446 | % first finite difference of the averaged results | |
fd=diff(fa); | 447 | 447 | fd=diff(fa); | |
% calculate Allan deviation for this tau | 448 | 448 | % calculate Allan deviation for this tau | |
M=length(fa); | 449 | 449 | M=length(fa); | |
sm(k)=sqrt(0.5/(M-1)*(sum(fd.^2))); | 450 | 450 | sm(k)=sqrt(0.5/(M-1)*(sum(fd.^2))); | |
451 | 451 | |||
% estimate error bars | 452 | 452 | % estimate error bars | |
sme(k)=sm(k)/sqrt(M+1); | 453 | 453 | sme(k)=sm(k)/sqrt(M+1); | |
454 | 454 | |||
455 | 455 | |||
end | 456 | 456 | end | |
457 | 457 | |||
if verbose == 2, fprintf(1,'\n'); end | 458 | 458 | if verbose == 2, fprintf(1,'\n'); end | |
calctime=toc; if verbose >= 2, fprintf(1,'allan: Elapsed time for calculation: %e seconds\n',calctime); end | 459 | 459 | calctime=toc; if verbose >= 2, fprintf(1,'allan: Elapsed time for calculation: %e seconds\n',calctime); end | |
460 | 460 | |||
461 | 461 | |||
else | 462 | 462 | else | |
error('allan: WARNING: no DATA.rate or DATA.time! Type "help allan" for more information. [err2]'); | 463 | 463 | error('allan: WARNING: no DATA.rate or DATA.time! Type "help allan" for more information. [err2]'); | |
end | 464 | 464 | end | |
465 | 465 | |||
466 | 466 | |||
%%%%%%%% | 467 | 467 | %%%%%%%% | |
%% Plotting | 468 | 468 | %% Plotting | |
469 | 469 | |||
if verbose >= 2 % show all data | 470 | 470 | if verbose >= 2 % show all data | |
471 | 471 | |||
% plot the frequency data, centered on median | 472 | 472 | % plot the frequency data, centered on median | |
if size(dtime,2) > size(dtime,1), dtime=dtime'; end % this should not be necessary, but dsplot 1.1 is a little bit brittle | 473 | 473 | if size(dtime,2) > size(dtime,1), dtime=dtime'; end % this should not be necessary, but dsplot 1.1 is a little bit brittle | |
try | 474 | 474 | try | |
% dsplot makes a new figure | 475 | 475 | % dsplot makes a new figure | |
hd=dsplot(dtime,medianfreq); | 476 | 476 | hd=dsplot(dtime,medianfreq); | |
catch ME | 477 | 477 | catch ME | |
figure; | 478 | 478 | figure; | |
if length(dtime) ~= length(medianfreq) | 479 | 479 | if length(dtime) ~= length(medianfreq) | |
fprintf(1,'allan: Warning: length of time axis (%d) is not equal to data array (%d)\n',length(dtime),length(medianfreq)); | 480 | 480 | fprintf(1,'allan: Warning: length of time axis (%d) is not equal to data array (%d)\n',length(dtime),length(medianfreq)); | |
end | 481 | 481 | end | |
hd=plot(dtime,medianfreq); | 482 | 482 | hd=plot(dtime,medianfreq); | |
if verbose >= 1, fprintf(1,'allan: Note: Install dsplot.m for improved plotting of large data sets (File Exchange File ID: #15850).\n'); end | 483 | 483 | if verbose >= 1, fprintf(1,'allan: Note: Install dsplot.m for improved plotting of large data sets (File Exchange File ID: #15850).\n'); end | |
if verbose >= 2, fprintf(1,' (Message: %s)\n',ME.message); end | 484 | 484 | if verbose >= 2, fprintf(1,' (Message: %s)\n',ME.message); end | |
end | 485 | 485 | end | |
set(hd,'Marker','.','LineStyle','none','Color','b'); % equivalent to '.-' | 486 | 486 | set(hd,'Marker','.','LineStyle','none','Color','b'); % equivalent to '.-' | |
hold on; | 487 | 487 | hold on; | |
488 | 488 | |||
% show center (0) | 489 | 489 | % show center (0) | |
plot(xlim,[0 0],':k'); | 490 | 490 | plot(xlim,[0 0],':k'); | |
% show 5x Median Absolute Deviation (MAD) values | 491 | 491 | % show 5x Median Absolute Deviation (MAD) values | |
hm=plot(xlim,[5*s.MAD 5*s.MAD],'-r'); | 492 | 492 | hm=plot(xlim,[5*s.MAD 5*s.MAD],'-r'); | |
plot(xlim,[-5*s.MAD -5*s.MAD],'-r'); | 493 | 493 | plot(xlim,[-5*s.MAD -5*s.MAD],'-r'); | |
% show linear fit line | 494 | 494 | % show linear fit line | |
hf=plot(xlim,polyval(s.linear,xlim)-s.median,'-g'); | 495 | 495 | hf=plot(xlim,polyval(s.linear,xlim)-s.median,'-g'); | |
title(['Data: ' name],'FontSize',FontSize+2,'FontName',FontName); | 496 | 496 | title(['Data: ' name],'FontSize',FontSize+2,'FontName',FontName); | |
%set(get(gca,'Title'),'Interpreter','none'); | 497 | 497 | %set(get(gca,'Title'),'Interpreter','none'); | |
xlabel('Time [sec]','FontSize',FontSize,'FontName',FontName); | 498 | 498 | xlabel('Time [sec]','FontSize',FontSize,'FontName',FontName); | |
if isfield(data,'units') | 499 | 499 | if isfield(data,'units') | |
ylabel(['data - median(data) [' data.units ']'],'FontSize',FontSize,'FontName',FontName); | 500 | 500 | ylabel(['data - median(data) [' data.units ']'],'FontSize',FontSize,'FontName',FontName); | |
else | 501 | 501 | else | |
ylabel('freq - median(freq)','FontSize',FontSize,'FontName',FontName); | 502 | 502 | ylabel('freq - median(freq)','FontSize',FontSize,'FontName',FontName); | |
end | 503 | 503 | end | |
set(gca,'FontSize',FontSize,'FontName',FontName); | 504 | 504 | set(gca,'FontSize',FontSize,'FontName',FontName); | |
legend([hd hm hf],{'data (centered on median)','5x MAD outliers',['Linear Fit (' num2str(s.linear(1),'%g') ')']},'FontSize',max(10,FontSize-2)); | 505 | 505 | legend([hd hm hf],{'data (centered on median)','5x MAD outliers',['Linear Fit (' num2str(s.linear(1),'%g') ')']},'FontSize',max(10,FontSize-2)); |