Loren on the Art of MATLAB

Turn ideas into MATLAB

Compose Yourself! 5

Posted by Loren Shure,

Even if we crunch numbers a lot, there are plenty of times, for example, when reporting, that we need to mix numbers into text. For a very long time, we've been able to create a character vector, and more recently, a string, with functions like sprintf. Look here for more information on character arrays and strings.

That's fine if we want to create one textual entity, but what if I need an array of them? I can use sprintf in a loop, or I can use a newer function, compose. Let me show you a bit how they each work.

Contents

A Single Numeric Input

Let's create some numeric values to use as possible inputs.

nums = [exp(1) pi]
nums =
    2.7183    3.1416

Here's character output from sprintf:

cs1 = sprintf('%d', nums(2))
cs1 =
    '3.141593e+00'

And here's the equivalent string output:

ss1 = sprintf("%d", nums(2))
ss1 = 
    "3.141593e+00"

Now let's try compose:

cc1 = compose('%d', nums(2))
cc1 =
  1×1 cell array
    {'3.141593e+00'}
sc1 = compose("%d", nums(2))
% What do we have in our workspace?
whos
sc1 = 
    "3.141593e+00"
  Name      Size            Bytes  Class     Attributes

  ans       1x4                 8  char                
  cc1       1x1               136  cell                
  ccm1      1x2               272  cell                
  cs1       1x12               24  char                
  csm1      1x26               52  char                
  nums      1x2                16  double              
  sc1       1x1               174  string              
  scm1      1x2               252  string              
  ss1       1x1               174  string              
  ssm1      1x1               206  string              
  tc        1x1               120  cell                
  tmpc      1x2               228  cell                
  tmps      1x2                 4  char                
  ts        1x2                 4  char                

What you may notice is that both sprintf and compose give the same output for the string version. However, for the character output, we get one character array using sprintf, but that same array embedded in a cell using compose.

clearvars -except nums

Multiple Numeric Inputs

Now let's see what happens if we are formatting output with more than a single numeric input.

csm1 = sprintf('%d ',nums)
ssm1 = sprintf("%d ",nums)
ccm1 = compose('%d',nums)
scm1 = compose("%d",nums)
csm1 =
    '2.718282e+00 3.141593e+00 '
ssm1 = 
    "2.718282e+00 3.141593e+00 "
ccm1 =
  1×2 cell array
    {'2.718282e+00'}    {'3.141593e+00'}
scm1 = 
  1×2 string array
    "2.718282e+00"    "3.141593e+00"
whos
  Name      Size            Bytes  Class     Attributes

  ccm1      1x2               272  cell                
  csm1      1x26               52  char                
  nums      1x2                16  double              
  scm1      1x2               252  string              
  ssm1      1x1               206  string              

What we see is that we get exactly the same types of output as before. But for the string version with compose, we get output matching the dimensions of the input, in this case, 1x2, whereas with the sprintf version, we get a single array for all of the cases. sprintf always produces a single array as output and recycles the hole (the formatter) while there is more data to format. compose returns arrays of text.

Handling Holes

Again, sprintf produces one output, regardless of the dimensions of the input.

tmps = sprintf('%d',[1 2])
tmpc = compose('%d',[1 2])
tmps =
    '12'
tmpc =
  1×2 cell array
    {'1'}    {'2'}

Also, sprintf truncates a hole if you don’t provide data. compose leaves it there in case you want to fill it with a subsequent call:

ts = sprintf('\n %s')

tc = compose('\n %s')
ts =
    '
      '
tc =
  1×1 cell array
    {'↵ %s'}

Your Turn

Do you need to format text/numbers? What techniques do you use to get the results you want? Let us know here.


Get the MATLAB code

Published with MATLAB® R2019a

5 CommentsOldest to Newest

Peter Wittenberg replied on : 1 of 5
If you want to make things simpler for me with MATLAB, consider that most of the time that I want numbers to turn into text strings is part of a text output, usually as part of a graph. Currently, I do the mathematical calculation and then insert it into text by using num2string, then use that in whatever text I am creating, either a cell array where necessary or a string. Typically I then put the string into a graph that I produce in MATLAB or some descriptive text for a result. So what would be easier for me is to allow me to put in numeric values into a text function such as [ ] or one on the concatenate commands. If the variable type was numeric and it was going into a string function, MATLAB would automatically use num2string so that I can just type the variable name and MATLAB will do they type conversion from numeric (typically the usual double precision) to text. I'm not against compose, but I don't see how it provides benefit beyond num2str.
Julian Thorp replied on : 2 of 5
Thanks for the post. I certainly like composing now having embraced this function since its arrival in MATLAB. It's really useful for constructing a series of labels which could include text and numbers. You might want these labels in plots (for axis-labels or datatips), or you could want them as RowNames in a table or category names for a categorical. compose provides a natural way to do this. Prior to the arrival of strings and compose I used to have constructs like the example below appearing all too frequently in my code.
>> a = 1:4;
>> [{} arrayfun(@(x)sprintf('Example %d',x), a, 'uniformoutput', false)]

ans =

  1×4 cell array

    {'Example 1'}    {'Example 2'}    {'Example 3'}    {'Example 4'}

Indeed 10 years ago I made a function to create sets of labels discrete spaces over several sets of axes strprod (string product) that worked on cellstr arrays
>> strprod({'amps ' 'volts '},  {'motor1' 'motor2'}, {' mean' ' min' ' max'})

  2×2×3 cell array

ans(:,:,1) = 

    {'amps motor1 mean' }    {'amps motor2 mean' }
    {'volts motor1 mean'}    {'volts motor2 mean'}


ans(:,:,2) = 

    {'amps motor1 min' }    {'amps motor2 min' }
    {'volts motor1 min'}    {'volts motor2 min'}


ans(:,:,3) = 

    {'amps motor1 max' }    {'amps motor2 max' }
    {'volts motor1 max'}    {'volts motor2 max'}

Nowadays I can use a string expression to create the same space
>> ["amps " "volts "]' + ["motor"+(1:2)+" "] + permute(["mean" "min" "max"], [3 1 2])

  2×2×3 string array

ans(:,:,1) = 

    "amps motor1 mean"     "amps motor2 mean" 
    "volts motor1 mean"    "volts motor2 mean"


ans(:,:,2) = 

    "amps motor1 min"     "amps motor2 min" 
    "volts motor1 min"    "volts motor2 min"


ans(:,:,3) = 

    "amps motor1 max"     "amps motor2 max" 
    "volts motor1 max"    "volts motor2 max"

>> 
Rob replied on : 3 of 5
I write a LOT of code to read in various text files, and converting UTC times (strings) to numbers has always been a pain, and painfully slow. The new datetime function helps a lot with that, so I'm not going to give an example with time, but converting various time strings to something Matlab can handle could be a good use of compose (when datetime fails you for not having that particular format as one of it's default ones). But another thing I do a lot is plot 2D data using imagesc, but plot log10 of the data. I then add a colorbar and it gives me ticks of powers of 10... which is never useful. So I always have to write extra code to make nice tick labels of 10^{something}. Matlab - make this a default feature! A colorbar for a log10 (or log) dataset! However, with compose I can speed up (and reduce the lines) of the code I need to do my log10 colorbar ticks labels. e.g. %% Draw some random data, and plot log10 of it figure(1) x = rand(10,10); % I want a log10 plot imagesc(log10(x)) % Add a colorbar, and give it a range from 0.001 to 1 (prior to the log10'ing') h = colorbar; set(gca,'CLIM',log10([0.001, 1])) set(h,'Ticks',-3:0) % -3 is log10(0.001), etc. %% Make the ticks the values I want them to be, using compose! set(h,'TickLabels', compose('10^{%d}',get(h,'Ticks'))) Done. That's neater than what I was using before! (Also, 1 line.)
Stuart McGarrity replied on : 4 of 5
Hi Loren, Cool. If your numbers are only integers (and not reals) , I assume you can also use: >>nums=1:5; >>strings(nums)
@Peter- I hear what you are saying. I do think it's pretty simple to do what you want now at least for simple cases. It would require a switch in notation from you. An easy example:
title("the value is "+number)
@Julian- So nice to hear that this saves you a lot of code and some hassle! Thanks for the illustration. @Rob- Please put in an enhancement request via tech support for your thoughts on the tick labels for the colorbar. In the meantime, at least you have a relatively simple way to accomplish your goal. Thanks! @Stuart- Sure you can use string (not strings):
>> nums = 1:5
nums =
     1     2     3     4     5
>> string(nums)
ans = 
  1×5 string array
    "1"    "2"    "3"    "4"    "5"

Add A Comment

Your email address will not be published. Required fields are marked *

Preview: hide