Home > src > sample.m

# sample

## PURPOSE

- sample object

## SYNOPSIS

This is a script file.

## DESCRIPTION

     - sample object

     copyright 2009-2012 Blair Armstrong, Christine Watson, David Plaut

        This file is part of SOS

        SOS is free software: you can redistribute it and/or modify
        it for academic and non-commercial purposes
        under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.  For commercial or for-profit
        uses, please contact the authors (sos@cnbc.cmu.edu).

        SOS is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

## CROSS-REFERENCE INFORMATION

This function calls:

- dataFrame - dataframe object
- population - population object
- sample - sample object
- validFileNameOrNull - helper function to validate that a string represents a valid file name or null
- validLogical - helper function to validate that a string represents a logical operator
- validStringOrNull - helper function to validate that a string represents a valid string or null
- verbosePrint - master print function that regulates cmd line output based on verbosity flags

This function is called by:

- genericFeederCandidateSelection - parent class for replacement selection objects
- population - population object
- randPopulationAndSampleCandidateSelection - random population and sample selection method for swaps
- sample - sample object
- sos - SOS optimization object

## SUBFUNCTIONS

- function obj = sample(n,varargin)
- function lockAll(obj)
- function unlockAll(obj)
- function obj = setPop(obj,population)
- function item = popItem(obj,itemIndex)
- function obj = appendItem(obj,item)
- function obj = swapItems(obj,sIndex,df,dfIndex,sosObj)
- function createLocks(obj)
- function p = sampleInputParser()
- function p = parseConstructorArgs(n,varargin)

## SOURCE CODE

    0001 % - sample object
    0002 %
    0003 % copyright 2009-2012 Blair Armstrong, Christine Watson, David Plaut
    0004 %
    0005 %    This file is part of SOS
    0006 %
    0007 %    SOS is free software: you can redistribute it and/or modify
    0008 %    it for academic and non-commercial purposes
    0009 %    under the terms of the GNU General Public License as published by
    0010 %    the Free Software Foundation, either version 3 of the License, or
    0011 %    (at your option) any later version.  For commercial or for-profit
    0012 %    uses, please contact the authors (sos@cnbc.cmu.edu).
    0013 %
    0014 %    SOS is distributed in the hope that it will be useful,
    0015 %    but WITHOUT ANY WARRANTY; without even the implied warranty of
    0016 %    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    0017 %    GNU General Public License for more details.
    0018
    0019 %    You should have received a copy of the GNU General Public License
    0020 %    along with SOS (see COPYING.txt).
    0021 %    If not, see <http://www.gnu.org/licenses/>.
    0022
    0023
    0024
    0025 classdef sample < dataFrame
    0026     %% creates and supports sample objects
    0027     %
    0028     % Additional functionality is inherited from parent class <dataFrame>
    0029     %
    0030     %PROPERTIES
    0031     %   n - target number of items for the sample (NOT necessarily current number of items)
    0032     %   population - population to derive the sample from
    0033     %   locks - int array of length <sampleObj>.n indicating if an item can be swapped (0) or not (1)
    0034     %   name - string name associated with the object
    0035     %
    0036     % ** ALSO manages the global property 'sampleCount', which tracks the
    0037     % number of samples that have been created.
    0038     %
    0039     %METHODS
    0040     %   sample(n,varargin) - Constructor - Creates a sample object
    0041     %   lockAll() - locks all the items in the sample
    0042     %   unlockAll() - locks all the items in the sample
    0043     %   setPop(population) - links a sample with a population
    0044     %   item = popItem(itemIndex) - pops the item from sample.data at itemIndex and returns it
    0045     %    appendItem(oitem) - append the item at {item index}
    0046     %   swapItems(,sIndex,df,dfIndex,sosObj) - swaps a sample item with a population item
    0047     %
    0048     %METHODS (STATIC)
    0049     %   p = sampleInputParser() - generates an input parser for the constructor
    0050     %
    0051     %METHODS (Access = private)
    0052     %   createLocks() - creates locks for sample items
    0053     %
    0054     %METHODS (Static, Access = private)
    0055     %   p = parseConstructorArgs(n,varargin) - parses arguments from sample constructor
    0056     %
    0057
    0058     %% PROPERTIES
    0059     properties
    0060         n % target number of items for the sample (NOT necessarily current number of items)
    0061         population % population to derive the sample from
    0062         locks % int array of length <sampleObj>.n indicating if an item can be swapped (0) or not (1)
    0063         name
    0064     end % properties
    0065
    0066
    0067     methods
    0068
    0069         %% sample CONSTRUCTOR
    0070         function obj = sample(n,varargin)
    0071         % Constructor - Creates a sample object
    0072         %
    0073         % CALL:
    0074         % sample(n, ['filename',<string>, 'isHeader',<logical>, 'isFormatting',<logical>, 'outFile',<string>])
    0075         %
    0076         % SYNOPSIS:
    0077         % Constructor - Creates a sample object and returns it
    0078         %
    0079         % PARAMETERS:
    0080         %  REQUIRED:
    0081         %   n - target number of observations for the sample
    0082         %
    0083         % OPTIONAL:
    0084         %   fileName - src file for the sample, which must
    0085         %       follow the SOS dataFrame format specifications.
    0086         %   isHeader/logical - param/logical-value pair indicating if
    0087         %       the source file has a header.  Defaults to false.
    0088         %   isFormatting/logical -  param/logical-value indicating if
    0089         %       the source file has formating.  Defaults to false.
    0090         %   outFile - param/string-value pair indicating the name
    0091         %       (inc. path, if other than pwd is desired) of
    0092         %       of file to save the sample in after
    0093         %       optimization has been completed.  Outfile is not
    0094         %       validated until write.  Defaults to 'null'
    0095         %   name/string - param/string-value pair indicating string name to
    0096         %       associate with the variable
    0097         %
    0098         % EXAMPLE:
    0099         %   s1 = sample(5); %creates a sample object targeted to have 5 observations
    0100         %   s2 = sample(10, 'fileName', 's1.txt'); % creates a sample
    0101         %                % object with target of 10 obs, reading in initial
    0102         %                % observations from s1.txt
    0103
    0104             verbosePrint([char(10) 'Creating and Configuring Sample Object'], ...
    0105                 'sample_constructor_startObjCreation');
    0106
    0107
    0108             p = sample.parseConstructorArgs(n,varargin);
    0109
    0110             global sampleCount;
    0111             if(isempty(sampleCount))
    0112                 curCount = 1;
    0113             else
    0114                 curCount = sampleCount + 1;
    0115             end
    0116
    0117             %override the default outFile with the sample number if a user
    0118             %specified value was not specified.
    0119             if any(strcmp(p.UsingDefaults,'outFile'))
    0120                 outFile = ['sample',num2str(curCount),'.out.txt'];
    0121             else
    0122                 outFile = p.Results.outFile;
    0123             end
    0124
    0125             %check that the outFile is associated with a valid file name.  If
    0126             %it's not, print a message to that effect
    0127             if ischar(outFile) == false || strcmp(outFile,'null')
    0128                 error('Outfile has not been set to a string != null.');
    0129             end
    0130
    0131             if (exist(outFile,'file'))
    0132                verbosePrint([char(10) 'WARNING: File {', outFile, '} already exists.  ', ...
    0133                    'If you attempt to write to this file, ',...
    0134                    'the existing one will be overridden' char(10)], ...
    0135                     'sample_constructor_fileExists');
    0136             end
    0137
    0138             %similary, use a default name based on the sample count if
    0139             %necessary
    0140              if any(strcmp(p.UsingDefaults,'name'))
    0141                  name = ['sample',num2str(curCount)];  %#ok<PROP>
    0142              else
    0143                  name = p.Results.name;  %#ok<PROP>
    0144              end
    0145
    0146             obj = obj@dataFrame('fileName',p.Results.fileName, ...
    0147                 'isHeader',p.Results.isHeader,'isFormatting', ...
    0148                 p.Results.isFormatting,'outFile',outFile);
    0149
    0150             obj.n = p.Results.n;
    0151
    0152             obj.name = name; %#ok<PROP>
    0153
    0154             %check that the user has not pre-loaded more data than the size
    0155             %of the sample.
    0156             if(isempty(obj.data) == 0)
    0157                 userN = length(obj.data{1});
    0158
    0159                 if(userN > obj.n)
    0160                     error('Sample Constructor: Pre-loaded sample size cannot be larger than target sample size {n}');
    0161                 end
    0162             end
    0163
    0164             obj.createLocks();
    0165
    0166             verbosePrint(['Creation of Sample Object Complete' char(10)], ...
    0167                 'sample_constructor_endObjCreation');
    0168
    0169             % creation of object successful, increment the counter
    0170             sampleCount = curCount;
    0171
    0172         end % constructor
    0173
    0174
    0175         %% lockAll() METHOD
    0176         function lockAll(obj)
    0177            % locks all the items in the sample
    0178            %
    0179            %EXAMPLE:
    0180            %    s1.lockAll();
    0181
    0182            if isempty(obj.data) == false
    0183                for i=1:length(obj.data{1}); obj.locks(i) = 1; end;
    0184            else
    0185                error('ERROR: no data in sample to lock');
    0186            end
    0187
    0188            verbosePrint(['All items in sample ', obj.name,' are now locked '], ...
    0189                     'sample_lockAll_end');
    0190         end
    0191
    0192         %% unlockAll() METHOD
    0193         function unlockAll(obj)
    0194            % locks all the items in the sample
    0195            %
    0196            %EXAMPLE:
    0197            %    s1.unlockAll();
    0198
    0199            for i=1:length(obj.locks); obj.locks(i) = 0; end;
    0200
    0201            verbosePrint(['All items in sample ', obj.name,' are now unlocked '], ...
    0202                     'sample_unlockAll_end');
    0203
    0204         end
    0205
    0206         %% setPop(population) METHOD
    0207         function obj = setPop(obj,population)
    0208             % links a sample with a population
    0209             %
    0210             %PARAMETERS:
    0211             %   population - a population object
    0212             %
    0213             %EXAMPLE:
    0214             %   s1.setPop(p1) % where s1 is a sample, p1 is a population
    0215
    0216             if(strcmp(class(population),'population') ~= 1)
    0217                 error('{population} must be a population object');
    0218             end
    0219
    0220             %ensure that the population is compatible with the sample as it
    0221             %is currently configured.
    0222
    0223             % if the sample has data, must make sure both the sample and
    0224             % the population have the same columns; otherwise, sample can
    0225             % just copy the population header info
    0226             if(isempty(obj.data) == 0)
    0227                [obj, population] = dataFrame.mergeHeaders(obj,population);
    0228             else
    0229                obj.header = population.header;
    0230                obj.format = population.format;
    0231             end
    0232
    0233            obj.population = population;
    0234
    0235            %link the population with the sample as well
    0236            population.addSample(obj);
    0237
    0238            verbosePrint(['Sample ', obj.name,'''s population set to: ',obj.population.name], ...
    0239                     'sample_setPop_end');
    0240
    0241         end % sample costructor
    0242
    0243
    0244
    0245         %% popItem(itemIndex) METHOD
    0246         function item = popItem(obj,itemIndex)
    0247             %pops the item from sample.data at itemIndex and returns it
    0248             %
    0249             %WARNING!!!:
    0250             % Currently, this function pops an item and then shifts up all
    0251             % of the subsequent data to fill the empty entries in the data.
    0252             % To avoid altering the object locks, this method currently
    0253             % will throw an error if the item at itemIndex, or a subsequent
    0254             % item, is locked.  This is a known issue, but should not
    0255             % interact with the standard swapping procedure or initial
    0256             % filling of the items.  If there is demand for this function
    0257             % its behavior may be corrected in the future.
    0258             %
    0259             %CALL:
    0260             % item =  <sampleObj>.popItem(itemIndex)
    0261             %
    0262             %SYNOPSIS:
    0263             % pops and returns the item at {itemIndex}
    0264             %
    0265             %PARAMETERS:
    0266             %   itemIndex - the row index of the to-be-popped item
    0267
    0268             p = inputParser;
    0269
    0270             p.addRequired('obj');
    0271             p.addRequired('itemIndex',@(itemIndex)validateattributes(itemIndex, {'numeric'}, ...
    0272                 {'scalar', 'integer', 'positive','>' 0}));
    0273             p.parse(obj,itemIndex);
    0274
    0275             if(isempty(obj.data) == true)
    0276                 error('No data in sample object - cannot pop item');
    0277             elseif itemIndex > length(obj.data{1})
    0278                 error('{itemIndex} exceeds row range of data array');
    0279             elseif (any(obj.locks(itemIndex:end)) == 1)
    0280                 error(['ERROR: Attempted to pop an item, when that item ', ...
    0281                     'or a subsequent one in obj.data has its obj.lock',...
    0282                     'active.  See the sample.popItem docmuentation for more info.']);
    0283             end
    0284
    0285             item = popItem@dataFrame(obj,itemIndex);
    0286         end
    0287
    0288
    0289         %% appendItem(item) METHOD
    0290         function obj = appendItem(obj,item)
    0291             % append the item at {item index}
    0292             %
    0293             % Warning!!!  Method does not confirm that the column structure
    0294             % of {item} (i.e., what columns of data are in what order)
    0295             % match that of the sample's data.  This should be the case if
    0296             % SOS is manipulating the items since it will only insert items
    0297             % into the sample that belong to the corresponding population,
    0298             % which should be in sync.  However, manual invocation of this
    0299             % method does not have this guarantee.  Use carefully!
    0300             %
    0301             %CALL:
    0302             % item =  <sampleObj>.popItem(itemIndex)
    0303             %
    0304             %SYNOPSIS:
    0305             % pops and returns the item at {itemIndex}
    0306             %
    0307             %PARAMETERS:
    0308             %   itemIndex - the row index of the to-be-popped item
    0309
    0310
    0311             %check to make sure haven't exceeded sample size.
    0312             if(isempty(obj.data) == false)
    0313                 if (length(obj.data{1}) +1 > obj.n)
    0314                     error('Sample already full.  Cannot append item.');
    0315                 end
    0316             end
    0317
    0318             appendItem@dataFrame(obj,item);
    0319         end
    0320
    0321         %% swapItems(sIndex,pIndex) METHOD
    0322
    0323         %% THIS CLASS NEEDS TO BE CORRECTED
    0324
    0325
    0326         function obj = swapItems(obj,sIndex,df,dfIndex,sosObj)
    0327             % swaps a sample item with an item from another datafame that shares the (or is the) population associated with that sample
    0328             %
    0329             %CALL:
    0330             %   swapItems(sIndex,df,dfIndex,sosObj)
    0331             %
    0332             %SYNOPSIS:
    0333             % swaps a sample item with an item from another data frame
    0334             % This dataframe must either be the, or share the, population with
    0335             % this sample.  This swap takes place both in the
    0336             % place both in the item's (raw) data and (normalized) zdata.
    0337             % Both objects must be associated with the same sosObj for the
    0338             % swap to occur.  This is to ensure that the normalized data
    0339             % that is being swapped is normalized relative to the other
    0340             % data in this SOS object, and not relative to some other SOS
    0341             % object of which this sample/population are also a part.
    0342             %
    0343             %PARAMETERS:
    0344             %   sIndex - index of sample item
    0345             %   df - another dataframe
    0346             %   dfIndex - index of population item
    0347             %   sosObj - sosObj that sample and population are linked to (and usually the object making the function call).
    0348             %
    0349
    0350             %Validate the inputs
    0351             p = inputParser;
    0352
    0353             p.addRequired('obj');
    0354             p.addRequired('sIndex',@(itemIndex)validateattributes(itemIndex, {'numeric'}, ...
    0355                 {'scalar', 'integer', 'positive','>' 0}));
    0356             p.addRequired('df',@(df)any(strcmp(superclasses(df),'dataFrame')));
    0357             p.addRequired('dfIndex',@(dfIndex)validateattributes(dfIndex, {'numeric'}, ...
    0358                 {'scalar', 'integer', 'positive','>' 0}));
    0359             p.addRequired('sosObj',@(sosObj)strcmp(class(sosObj),'sos'));
    0360
    0361
    0362
    0363             p.parse(obj,sIndex,df,dfIndex,sosObj);
    0364
    0365             %check that the population and sample exist and contain
    0366             %normalized data for the right SOSobj
    0367
    0368             %first make sure both the sample and the dataframe have data
    0369             if(isempty(obj.data) == true)
    0370                 error('Sample data is empty');
    0371             end
    0372
    0373
    0374             if(isempty(df.data) == true)
    0375                 error('Population data is empty');
    0376             end
    0377
    0378             %check that sample and dataframe have been associated with the
    0379             %current SOSobject, which should indicate whether any
    0380             %normalized data was normalized for this particular SOS object.
    0381
    0382             if(obj.sosObj ~= sosObj)
    0383                 error('Sample is not associated with SOS object executing match');
    0384             end
    0385
    0386             if(df.sosObj ~= sosObj)
    0387                 error('Dataframe is not associated with SOS object executing match');
    0388             end
    0389
    0390
    0391             %check that the sample and dataframe both share the same
    0392             %population
    0393             if(obj.population ~= df)
    0394                 if(strcmp(class(df),'sample'))
    0395                     if(obj.population ~= df.population)
    0396                         error('Sample and Dataframe do not share a population');
    0397                     end
    0398                 else
    0399                     error('If df is not the population for the sample, it should be a sample itself');
    0400                 end
    0401
    0402             end
    0403
    0404
    0405             %check that the sample and the population contain normalized
    0406             %data.
    0407             if(isempty(obj.zdata) || isempty(obj.population.zdata))
    0408                 error('df or sample do not contain normalized data.Data must be normalized prior to swaps');
    0409             end
    0410
    0411             %validate the indices for the swap:
    0412             if(dfIndex > length(df.data{1}))
    0413                 error('df index exceeds number of rows in population');
    0414             end
    0415
    0416             if(sIndex > length(obj.data{1}))
    0417                 error('sample index exceeds number of rows in sample');
    0418             end
    0419
    0420             if(obj.locks(sIndex) == 1)
    0421                 error('item at specified sample index is locked');
    0422             end
    0423
    0424             if(strcmp(class(df),'sample'))
    0425                 if (df.locks(dfIndex) == 1)
    0426                     error('item at specified df index is locked');
    0427                 end
    0428             end
    0429
    0430
    0431
    0432             % copy the sample; again, assumes population and sample header
    0433             % syncing works as designed
    0434             sampleItemData = cell(1,length(obj.header));
    0435             sampleItemzData = cell(1,length(obj.header));
    0436             for i=1:length(obj.header)
    0437                 sampleItemData{i} = obj.data{i}(sIndex);
    0438                 sampleItemzData{i} = obj.zdata{i}(sIndex);
    0439             end
    0440
    0441             %move the df item into the sample, then move the copied
    0442             % sample item into the df
    0443             for i=1:length(obj.header)
    0444
    0445                 %try to speed up processing of strings by using a more
    0446                 %primitive operation to copy numbers;  Some more fancy
    0447                 %string wrapping will be necessary in the case of strings.
    0448
    0449                 if strcmp(obj.format{i},'%f')
    0450                     obj.data{i}(sIndex) = df.data{i}(dfIndex);
    0451                     obj.zdata{i}(sIndex) = df.zdata{i}(dfIndex);
    0452
    0453                    df.data{i}(dfIndex) = sampleItemData{i};
    0454                    df.zdata{i}(dfIndex) = sampleItemzData{i};
    0455
    0456                 else
    0457
    0458                     obj.data{i}(sIndex) = df.data{i}(dfIndex);
    0459                     obj.zdata{i}(sIndex) = df.zdata{i}(dfIndex);
    0460
    0461                     %trying to optimize performance by changing these to sub
    0462                     %cell references
    0463                     % used to be:
    0464                     %
    0465
    0466                    %df.data{i}(dfIndex) = sampleItemData{i};
    0467                    %df.zdata{i}(dfIndex) = sampleItemzData{i};
    0468
    0469                    df.data{i}(dfIndex) = sampleItemData{i};
    0470                    df.zdata{i}(dfIndex) = sampleItemzData{i};
    0471                 end
    0472
    0473             end
    0474         end % swapItems()
    0475
    0476     end
    0477
    0478     methods (Access = private)
    0479
    0480         %% createLocks() PRIVATE METHOD
    0481         function createLocks(obj)
    0482             % creates locks for sample items, using 'isLocked' data from a user-specified file, if applicable
    0483
    0484             lockIndex=-1;
    0485             for i=1:length(obj.header)
    0486                if(strcmp(obj.header{i},'isLocked'))
    0487                    lockIndex=i;
    0488                end
    0489             end
    0490
    0491             % if locks were specified, move them to the locks array and
    0492             % remove them from the data file
    0493             if(lockIndex >= 0)
    0494                 obj.header(lockIndex) = [];
    0495                 obj.format(lockIndex) = [];
    0496
    0497                 if(isempty(obj.data) == 0)
    0498                     obj.locks=[obj.data{lockIndex} ...
    0499                         zeros(1,(obj.n-length(obj.data{1})))];
    0500                     obj.data(lockIndex) = [];
    0501                 else
    0502                     obj.locks=zeros(1,obj.n);
    0503                 end
    0504             else
    0505                 obj.locks=zeros(1,obj.n);
    0506             end
    0507
    0508         end % createLocks
    0509
    0510
    0511     end
    0512
    0513     methods (Static)
    0514
    0515         %% p = sampleInputParser() STATIC METHOD
    0516         function p = sampleInputParser()
    0517             %generates an input parser with parameter/value pairs and validators for the constructor
    0518
    0519             p = inputParser;
    0520
    0521             %Define required and optional arguments, and specify how they
    0522             %are to be validated
    0523
    0524             p.addRequired('n',@(n)validateattributes(n, {'numeric'}, ...
    0525                 {'scalar', 'integer', 'positive', '>', 0}));
    0526             p.addParamValue('fileName','null', ...
    0527                 @(fileName)validFileNameOrNull(fileName));
    0528             p.addParamValue('isHeader',false, ...
    0529                 @(isHeader)validLogical(isHeader));
    0530             p.addParamValue('isFormatting',false, ...
    0531                 @(isFormatting)validLogical(isFormatting));
    0532             p.addParamValue('name','noname', ...
    0533                 @(name)ischar(name));
    0534             %Note that outfile is not validated at this point; this will be
    0535             %done when it comes time to write to the file.  Motivation is
    0536             %that I don't want to write to create a file (and directory
    0537             %structure) unless the user ultimately wants to write
    0538             %something.
    0539             p.addParamValue('outFile','null', ...
    0540                 @(outFile)validStringOrNull(outFile));
    0541         end % sampleInputParser
    0542     end
    0543
    0544
    0545     methods (Static, Access =private)
    0546
    0547         %% p = parseConstructorArgs(n,varargin) STATIC PRIVATE METHOD
    0548         function p = parseConstructorArgs(n,varargin)
    0549             % parses arguments from sample constructor
    0550             %
    0551             %CALL:
    0552             % p = sample.parseConstructorArgs(n,varargin)
    0553             %
    0554             %SYOPSIS:
    0555             %parses the arguments from the sample constructor.  Default
    0556             %values are substituted where appropriate.  Returns a struct
    0557             %with the parsed args.
    0558             %
    0559             %PARAMETERS:
    0560             % SAME as population CONSTRUCTOR
    0561             %
    0562             %RETURNS:
    0563             % the parsed constructor arguments in struct format
    0564
    0565             %this cell gets recursively wrapped when it is passed, so
    0566             %unwrap one layer.
    0567
    0568             varargin = varargin{1};
    0569
    0570             p = sample.sampleInputParser();
    0571
    0572             p.parse(n,varargin{:});
    0573
    0574         end % parseConstructorArgs
    0575
    0576     end
    0577 end
    0578