Code covered by the BSD License  

Highlights from
Display Formatted Text Table of Data

Display Formatted Text Table of Data

by

 

10 Nov 2011 (Updated )

Display data, mixed numeric and strings with optional col and row headers and your choice of col sep

displaytable(data, colheadings, wid, fms, rowheadings, fid, colsep, rowending)
function displaytable(data, colheadings, wid, fms, rowheadings, fid, colsep, rowending)
% Prints formatted matrix of numerical data with headings
% 
% Syntax
% 
% displaytable(data, colheadings, wid, fms, rowheadings, fid, colsep, rowending)
% 
% Input
% 
% data: a matrix or cell array, containing the data to be put in the table.
% If a matrix the numbers in the table will be printed using the default
% format specifier (f), or the specifier(s) supplied in fms. data can also
% be a cell array containing a mixture of strings and numbers. each cell in
% this case can contain only a single scalar value. In this case numbers
% are printed as in the matrix case, while strings are printed up to the
% maximum width of the column.
% 
% colheadings: a cell array of strings for the headings of each column. Can
% be an empty cell array if no headings are required. If no column widths
% are supplied (see below), the columns will be made wide enough to
% accomdate the headings.
% 
% wid: (optional) scalar or vector of column widths to use for the table.
% If scalar, every column will have the same width. If a vector it must be
% of the same length as the number of columns of data. If not supplied, and
% column headers are supplied, the width of the widest column header will
% be used. If not supplied and column headers are not supplied, a default
% with of 16 characters is used.
% 
% fms: (optional) a string, or cell array of strings containing format
% specifiers for formatting the numerical output in each column. If a
% single string, the same specifier is used for every column. If not
% supplied, the 'g' specifier is used for every column.
% 
% rowheadings: (optional) a cell array of strings for the start of each
% row. Can be an empty cell array if no row headings are required. If row
% headings are supplied, the first column will be made wide enough to
% accomodate all the headings.
% 
% fid: (optional) the file id to print to. Use 1 for stdout (to print to
% the command line). 
% 
% colsep: (optional) A string or character to insert between every column.
% The default separation string is ' | ', i.e. a space followed by a
% vertical bar, followed by a space. A table suitible for inclusion in a
% LaTeX document can be created using the ' & ' string, for example.
% 
% rowending: (optional) An optional string or character to be appended at
% the end of every row. Default is an empty string.
% 
% Example 1 - Basic useage
% 
% colheadings = {'number of projects','sales','profit'};
% rowheadings = {'Jimmy Slick', 'Norman Noob'}
% data = [3 rand(1) rand(1); 1 rand(1) rand(1)];
% 
% To format the first number in each row as a decimal (%d), the second
% number %16.4f, and the third as %16.5E do the following:
% 
% wid = 16;
% fms = {'d','.4f','.5E'};
% 
% In this case 16 will be the field width, and '.5E' is what to use for the
% fms argument
% 
% fileID = 1;
% 
% >> displaytable(data,colheadings,wid,fms,rowheadings,fileID);
%             |number of projec |           sales |          profit 
% Jimmy Slick |               3 |          0.4502 |    5.22908E-001 
% Norman Noob |               1 |          0.9972 |    2.78606E-002 
%
% Example 2 - Produce a latex table
% 
% colheadings = {'number of projects','sales','profit'};
% rowheadings = {'Jimmy Slick', 'Norman Noob'};
% data = [3 rand(1) rand(1); 1 rand(1) rand(1)];
% wid = 16;
% fms = {'d'};
% 
% colsep = ' & ';
% rowending = ' \\';
% 
% fileID = 1;
% 
% >> displaytable(data,colheadings,wid,fms,rowheadings,fileID,colsep,rowending);
%
%             & number of projec &            sales &           profit \\
% Jimmy Slick &                3 &    6.948286e-001 &    3.170995e-001 \\
% Norman Noob &                1 &    9.502220e-001 &    3.444608e-002 \\
% 
% Example 3 - Mixed numeric and strings
%
% colheadings = {'number of projects','sales','profit'};
% rowheadings = {'Jimmy Slick', 'Norman Noob'};
% 
% data = {3, rand(1), rand(1); 
%         1, 'none', 0};
%     
% wid = 16;
% fms = {'d','.4f','.5E'};
% 
% fileID = 1;
% 
% >> displaytable(data,colheadings,wid,fms,rowheadings,fileID);
%             | number of projec |            sales |           profit
% Jimmy Slick |                3 |           0.4854 |     8.00280E-001
% Norman Noob |                1 |             none |     0.00000E+000
%
% See Also: DISP, FPRINTF

% Created by Richard Crozier 2012

    if nargin < 8 || isempty(rowending)
        rowending = '';
    end

    if nargin < 7 || isempty(colsep)
        colsep = ' | ';
    end

    % do some basic checking of input
    if nargin < 6 || isempty(fid)
        % print to the command line
        fid = 1;
    end
    
    if nargin < 5 || isempty(rowheadings)
        % no row headings supplied, use empty cell array
        rowheadings = {};
    end
    
    if nargin < 4 || isempty(fms)
        % no format specifiers supplied, use 'g' for all columns
        fms = 'g';
    end
    
    if nargin < 3 || isempty(wid)
        % default width is 10, this will be modified if column headers are
        % supplied
        wid = 10;
    end
    
    if nargin < 2
        colheadings = {};
    end

    if nargin >= 6 && ~iscellstr(rowheadings)
        error ('row headings must be cell array of strings');
    end
    
    % get the numbers of rows and columns of data
    [nRowD,nColD] = size(data);
    
    % check that sensible format specifiers have been supplied
    if ~iscellstr(fms)
        
        if ischar(fms)
            fms = repmat({fms},1,nColD);
        else
            error('fms must be a string or cell array of strings.');
        end
        
    elseif isempty(fms)
        
        fms = repmat({'f'},1,nColD);
        
    elseif numel(fms) ~= nColD
        
        if numel(fms) == 1
            fms = repmat(fms,1,nColD);
        else
           error('fms does not have the same number of format specifiers as there are columns.');
        end
        
    end
    
    % replace empty format specifiers with 'f'
    for i = 1:numel(fms)
        if isempty(fms{i})
            fms{i} = 'f';
        end
    end
    
    [nRowFms,nColFms] = size(fms);
    if(nRowFms>1)
        error ('fms can not have more than one row');
    end
    

    if ~isempty(rowheadings)
        
        if ~iscellstr(rowheadings)
            error('rowheadings must be a cell array of strings');
        end
        
        if numel(rowheadings) ~= size(data, 1)
            error('Rowheadings must be a cell array of strings of the same size as the number of rows in data.')
        end
    
        rhwid = 0;

        for r = 1:numel(rowheadings)
            % find the maximum width the first column must be to accomodate
            % the row headings
            rhwid = max(rhwid, length(rowheadings{r}));
        end
        
    end
    
    if isscalar(wid)
        
        tempwid = zeros(1, numel(fms));

        for i = 1:numel(fms)

            % get the number of decimal places specified
            [start_idx, end_idx, extents, matches] = regexp(fms{i}, '(?<=\.)\d+');

            if isempty(start_idx)

                % no numbers were found in the format spec, just use the
                % supplied width
                tempwid(i) = wid;

            else

                % some numbers were supplied, use the larger of the width
                % or the number of desired decimal places plus two (to
                % allow for leading number plus decimal point)
                tempwid(i) = max(wid, round(str2double(matches{1})) + 2);

            end

        end

        % replace scalar width with array of width values
        wid = tempwid;
        
    end
        
    if ~isempty(colheadings)
        
        [nRowCH,nColCH] = size(colheadings);
        if(nRowCH>1)
            error ('column headings can not have more than one row');
        end
        
        if ~iscellstr(colheadings)
            error('If not empty, colheadings must be a cell array of strings');
        end
        
        if(nColCH ~= nColD)
            error ('data must have same number of columns as headings');
        end
    
%         fmt = arrayfun(@(x) ['%',num2str(wid(x)),'s |'], 1:nColD, 'UniformOutput', false);

        if ~isempty(rowheadings)
            % TODO allow extra heading for column
            fprintf(fid, ['%s',colsep], repmat(' ', 1,rhwid));
        end
        
        if nargin < 3
            
            % only data and column headings have been supplied, so
            % determine a sensible value for the width of each column
            
            tempwid = zeros(size(wid));
            for i = 1:numel(colheadings)

                % get a column width which is the minimum to accept the
                % column heading length or the default width specification
                tempwid(i) = max(length(colheadings{i}), wid(i));
                
                if tempwid < 1
                    error('Column width is less than 1, and the column header is empty.')
                end
                
            end
            
            wid = tempwid;
            
        end
        
        % Now loop through the column headings printing them out with the
        % desired column separator
        for i = 1:numel(colheadings)
            
            str = sprintf(['%',num2str(wid(i)),'s'],colheadings{i});

            if i == numel(colheadings)
                % If we are at the end of a row, don't print the column
                % separator
                fprintf(fid, '%s', str(1:wid(i)) );
            else
                % print the column header and column separator
                fprintf(fid, ['%s',colsep], str(1:wid(i)) );
            end
            
        end
        
        fprintf(fid, '%s\n', rowending);

    end
    
    fmt = arrayfun(@(x) ['%',num2str(wid(x)),fms{x}],1:nColD,'UniformOutput',false);
    
    for i = 1:size(data,1)
        
        % first print a row header if necessary
        if ~isempty(rowheadings)
            fprintf(fid, ['%',num2str(rhwid),'s',colsep], rowheadings{i});
        end
            
        % now loop through the data formatting and printing as appropriate
        for j = 1:size(data,2)

            if iscell(data)
                % data is a cell array, possibly containing mixed data
                % types

                if ischar(data{i,j})
                    
                    str = sprintf(['%',num2str(wid(j)),'s'],data{i,j});
                    
                elseif isscalar(data{i,j})
                    
                    % write the number 
                    str = sprintf(fmt{j},data{i,j});
                    
                    if length(str) > wid(j)

                        % convert to scientific notation as it doesn't fit
                        str =  sprintf(['%',num2str(wid(j)),'g'],data{i,j});

                        if length(str) > wid(j)
                            % fill space with #s as the number stil doesn't
                            % fit in
                            str = repmat('#', 1, wid(j));
                        end

                    end

                elseif isempty(data{i,j})
                    
                    % indicate an empty value 
                    str = sprintf(['%',num2str(wid(j)),'s'],'');
                    
                else
                    % we can only tabulate strings and scalars, so throw an
                    % error
                    error('CROZIER:displaytable:badcellcontents', ...
                          'each cell in cell array data must contain only individual strings or scalar values.')
                
                end
                
                % print the string
                fprintf(fid, '%s', str(1:wid(j)));
                
                if j < size(data,2)
                    % print column separator
                    fprintf(fid, colsep);
                end

            else
                
                % data is a numerical matrix

                str = sprintf(fmt{j},data(i,j));

                if length(str) > wid(j)
                    
                    % convert to scientific notation as it doesn't fit
                    str =  sprintf(['%',num2str(wid(j)),'g'],data(i,j));
                    
                    if length(str) > wid(j)
                        % fill space with #s as the number doesn't fit in
                        str = repmat('#', 1, wid(j));
                    end
                    
                end
                
                if j == size(data,2)
                    % do not print the last column separator at the end of
                    % a row
                    fprintf(fid, '%s', str(1:wid(j)));
                else
                    fprintf(fid, ['%s',colsep], str(1:wid(j)));
                end
            end
            
        end
        
        % end of line so put in a new row
        fprintf(fid, '%s\n', rowending);
        
    end

end

Contact us