I have a structure with scalar fields, say mom, and I would like to display the values of the structure on the screen in aligned columns, possibly with some header. Here is a minimum working example:
mom.a = 1;
mom.b = 2;
mom.veryLongName = 3;
header = {'Moment'; 'Value'};
fnames = fieldnames(mom);
pvec = zeros(numel(fnames),1);
for i = 1:numel(fnames)
pvec(i) = mom.(fnames{i});
end
fprintf('%s \t \t %s \n',header{1},header{2});
for i = 1:numel(fnames)
fprintf('%s \t \t %8.3f \n',fnames{i},pvec(i));
end
This code works OK but the problem is that columns are not aligned, especially if a field has a very long name. This is the output:
Moment Value
a 1.000
b 2.000
veryLongName 3.000
You can convert the struct to a table, and let MATLAB format the display for you:
disp(struct2table(mom))
output:
a b veryLongName
_ _ ____________
1 2 3
Alternatively, use the field width specifier for the fprintf format string:
width = max(cellfun('length', fnames));
width = max(width, length(header{1}));
fprintf('%-*s %s\n', width, header{1}, header{2});
for i = 1:numel(fnames)
fprintf('%-*s %-8.3f\n', width, fnames{i}, mom.(fnames{i}));
end
output:
Moment Value
a 1.000
b 2.000
veryLongName 3.000
%-*s is the same as %s, but with an - flag to indicate left-alignment, and the * width specifier, which indicates getting the width from a parameter. So %-*s reads two values from the parameters: first a width, then a string.
Related
I have this code to load data from text file, four columns - string, 3 integer columns and decimal number.
fileID = fopen(Files(j,1).name);
formatSpec = '%*s %d %d %d %f';
data = zeros(0, 4, 'double');
k = 0;
while ~feof(fileID)
k = k+1;
temp = textscan(fileID,formatSpec,10,'HeaderLines',1);
data = [data; [ temp{:} ] ] ;
end
fclose(fileID);
In a temp variable, the last column is saved as decimal number, however, the command
data = [data; [ temp{:} ] ]
has as consequence its rounding and I get 0 or 1 in last column.
I am asking why is that?
temp looks like (1x4 cell):
[5248000;5248100;....] [5248100;5248200;....]
[111;95;....] [0,600000000000000;0,570000000000000;....]
data then looks like (1x4 matrix):
5248000 5248100 111 1
5248100 5248200 95 1
EDIT:
Retesting (recreation of the same variable, just copied numbers from variable editor)
temp=cell(1,4);
temp{1}=[0;100;200;300;400;500;600;700;800;900];
temp{2}=[100;200;300;400;500;600;700;800;900;1000];
temp{3}=[143;155;150;128;121;122;137;145;145;126];
temp{4}=[0.340000000000000;0.450000000000000;0.370000000000000;...
0.570000000000000;0.570000000000000;0.580000000000000;...
0.590000000000000;0.500000000000000;0.460000000000000;0.480000000000000];
tempx=[temp{:}]
This makes it correctly! The last columnd is decimal.
But why it doesn't work on "real" data from textscan function?
Consider reading everything as floating point values:
change
formatSpec = '%*s %d %d %d %f';
to
formatSpec = '%*s %f %f %f %f';
If works in your EDIT because your variables are already of type double.
Concatenating double and int data casts to the int type. For example:
>> [pi; int16(5)]
ans =
2×1 int16 column vector
3
5
To avoid that, you can cast to double before concatenating. So in your case use something like the following to convert each cell's content to double(thanks to #CrisLuengo for a correction):
[ data; cellfun(#double, temp, 'uniformoutput', true) ]
My general task is: Create a Matlab function that convert matrix results to LaTeX format that I will be able to just copy and paste it to the LaTeX source code.
I found the latex() function in Matlab that helps me a lot. But there is a problem with the number of decimals. I found that the vpa() function could help by setting the precision. But if I do
digits(precision);
t = latex(sym(vpa(A)));
it does not work as I expect. For example for
A = [0.00101; 0.01010; 0.10101;
1.10101; 1.01010; 1.00101;
11.10101; 11.01010; 11.00101]
digits(5);
latex(sym(vpa(A)))
I get
ans =
'\left(\begin{array}{c}
0.00101\\ 0.0101\\ 0.10101\\ 1.101\\ 1.0101\\ 1.001\\ 11.101\\ 11.01\\ 11.001
\end{array}\right)'
The vpa() function returns (from doc) 'at least d significant digits' - not the decimals. I know. Is there any way how to arrange that I always get max. 5 decimals? Thus:
ans =
'\left(\begin{array}{c}
0.00101\\ 0.0101\\ 0.10101\\ 1.10101\\ 1.0101\\ 1.00101\\ 11.10101\\ 11.0101\\ 11.00101
\end{array}\right)'
You can carefully loop through the columns and rows, formatting the entries with sprintf.
% Matrix to be rendered with LaTeX
A = [rand(4,4) + eye(4); [1;1;0;0]];
% sprintf string format - here, a float with 3 decimal places
% padded with whitespace to be at least 6 characters long
strfmt = '%6.3f';
% Initialise string - could be initialised as e.g.
% s = sprintf('\\begin{pmatrix}\n');
s = '';
% Print entries of the first row of A, with columns separated by ampersands (&)
s = strcat(s, sprintf(strfmt, A(1,1)));
for c = 2:columns
s = strcat(s, sprintf([' & ', strfmt], A(1, c)));
end % for c
% Print rows of A, with lines separated by newline
% (\\ - escaped as \\\\, then \n is newline within the string, not required)
for r = 2:rows
s = strcat(s, sprintf([' \\\\\n', strfmt], A(r, 1)));
for c = 2:columns
s = strcat(s, sprintf([' & ', strfmt], A(r, c)));
end % for c
end % for r
% could finish with e.g.
% s = strcat(s, sprintf('\n\\end{pmatrix}\n');
The output here is
s =
10.815 & 0.632 & 0.958 & 0.957 & 1.000 \\
0.906 & 10.098 & 0.965 & 0.485 & 1.000 \\
0.127 & 0.278 & 10.158 & 0.800 & 0.000 \\
0.913 & 0.547 & 0.971 & 10.142 & 0.000
which you can surround with your favourite latex matrix environment (I like pmatrix which requires the amsmath package).
In the problem given in the question, A would be the matrix to be rendered, and strfmt would be %.5f.
I would like to concatenate some matrices (20-30). I wrote a simple function:
function [ appendedResults ] = appendResults( startNumber, endNumber )
str2 = '_simulation.xlsx';
str1 = num2str(startNumber);
InputFilename = strcat(str1, str2);
appendedResults = uint16( xlsread(InputFilename ) );
for i= startNumber+1 : endNumber
temp_simulationResults = uint16( xlsread(InputFilename ) );
appendedResults = vertcat( appendedResults, temp_simulationResults );
end
filename1 = strcat('appended', str2);
xlswrite(filename1, appendedResults);
end
It's works fine if I use 3-4 matrices. But when I'm using more, I get an error:
Error using xlswrite (line 219)
Error: Not enough memory
Then the matlab use 6 GigaByte (!!) memory. The matrices are only 7 MegaByte!
I wrote another function with for loops( without Matlab Vertcat function).
I get the same error!
function [ appendedResults ] = appendResultsInOneMatrix( startNumber, endNumber )
str2 = '__simulation.xlsx';
str1 = num2str(startNumber);
InputFilename = strcat(str1, str2);
numberOfItems = (endNumber - startNumber) + 1 ;
firstResults = uint16( xlsread(InputFilename ) );
[row , col] = size(firstResults);
appendedResults = zeros ( row * numberOfItems , col);
for i=1: row
for j=1:col
appendedResults( i , j ) = firstResults( i , j);
end
end
for i= startNumber+1 : endNumber
str1 = num2str(i);
InputFilename = strcat(str1, str2);
temp_simulationResults = uint16( xlsread(InputFilename ) );
[temp_row , temp_col] = size(temp_simulationResults);
for m = 1 : temp_row
for n = 1 : temp_col
appendedResults( m+row , n ) = temp_simulationResults( m , n);
end
end
end
filename1 = strcat('appended', str2);
xlswrite(filename1, appendedResults);
end
What's wrong?
If you concatenate matrices, the resulting dimensions will be the maxima of the individual dimensions of the input matrices times the number of matrices; for example, if matrix A is 1x1,000,000 and matrix B is 1,000,000x1, the individual storage of these matrices would only be 1 million elements, but the resulting matrix would need to store 2* 1012 elements!
EDIT: also, Excel files definitely are not the storage you should be using for tables with more than a million entries!
EDIT2: Take a piece of paper. Draw a 1cm x 10cm (width x height) rectangle on it, write "matrix A" on it so you know how the rectangle is oriented and don't accidentially rotate it. Cut it out. Draw a 10cm x 1cm rectange, write "matrix B" on it, for the same reason.
Concatenate both rectangles. What is the area of the rectangle you need to enclose this shape? What's the sum of areas of the "matrix A" and "matrix B" rectangles?
Matrices are rectangular, and by default you will have to have enough space to store the minimum rectangle that contains the concatenation of your matrices. You can change that by using sparse matrices in Matlab (that's a long topic, and can't be explained in a single StackOverflow answer), but that won't change the fact that what you're doing probably doesn't make the least sense.
I have a script which fits some optical data to a sum of Lorentzian oscillators, and then spits out a figure with the original data and the fit. I would also like to include a text annotation with a table of the fitting parameters, but cannot figure out how to get rows and columns in my text box.
Each peak has 3 parameters and then there are 3 more global fitting parameters. My first try was to do this:
ParamTableLabels = {'\omega_p (cm^{-1})', '\omega_0 (cm^{-1})', '\Gamma (cm^{-1})'};
ParamTableVals = num2cell(Ef);
ParamTableLabels2 = {'d (\mu{m})','\epsilon_\infty','Scale'};
ParamTableVals2 = {ThickFit,EinfFit,ScaleFit};
ParamTable = vertcat(ParamTableLabels,ParamTableVals,ParamTableLabels2,ParamTableVals2);
where Ef is my 3xN matrix of fitting parameters. After generating my figure, I try to place the table in my plot at a suitable set of coordinates X,Y using:
text(X,Y,ParamTable)
and the result is a single column of text, no rows. My second attempt, which sort of works is to break up each column:
text(X, Y,ParamTable(:,1));
text(X+dX, Y,ParamTable(:,2));
text(X+2*dX,Y,ParamTable(:,3));
This almost works, but the subscripts in the labels throw off the vertical alignment of the last few rows, and it takes an undue amount of tinkering to get the spacing correct. I'm spending more time trying to get the text box to look right than to do the actual modelling.
How can I programatically format a block of text, containing both labels and variables, into rows and columns, and then use it as a text annotation in a figure with minimal user tinkering?
This is a not well supported using basic commands. But you can at least save yourself the trouble of guessing the subsequent X positions by making Matlab do the work for you.
The key is the "Extent" read-only parameter attached to a text block. Use docsearch text properties to see the documentation.
Putting this into some code:
padFraction = 0.1; %This is roughly the unitless padding between columns, as a fraction of the column on the left.
curX = X; %Leave the initial X variable unchanged
%For each text block column, add the text block, get the extent, and adjust curX
h = text(curX, Y,ParamTable(:,1));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
h = text(curX, Y,ParamTable(:,2));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
text(curX,Y,ParamTable(:,3));
The full script used to generate/test is below:
ParamTableLabels = {'\omega_p (cm^{-1})', '\omega_0 (cm^{-1})', '\Gamma (cm^{-1})'};
Ef = round(rand(10,3)*100);
ParamTableVals = num2cell(Ef);
ParamTableLabels2 = {'d (\mu{m})','\epsilon_\infty','Scale'};
ParamTableVals2 = {'ThickFit','EinfFit','ScaleFit'};
ParamTable = vertcat(ParamTableLabels,ParamTableVals,ParamTableLabels2,ParamTableVals2);
X = 1; Y = 1.1;
%Put something in the plot
figure(1); clf; hold on;
plot(-10:10, randn(21,1)*20,'.');
codeblock = 3;
switch codeblock
case 1
text(X,Y,ParamTable)
case 2
dX = 3;
text(X, Y,ParamTable(:,1));
text(X+dX, Y,ParamTable(:,2));
text(X+2*dX,Y,ParamTable(:,3));
case 3
padFraction = 0.1;
curX = X;
h = text(curX, Y,ParamTable(:,1));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
h = text(curX, Y,ParamTable(:,2));
curExtent = get(h, 'Extent');
curX = curExtent(1) + curExtent(3)*(1+padFraction);
text(curX,Y,ParamTable(:,3));
end
I have a textfile with the following structure:
1999-01-04
1,100.00
1,060.00
1,092.50
0
6,225
1,336,605
37
1999-01-05
1,122.50
1,087.50
1,122.50
0
3,250
712,175
14
...
The file contains repeated sets of eight values (a date followed by seven numbers, each on their own line).
I want to read it into MATLAB and get the values into different vectors. I've tried to accomplish this with several different methods, but none have worked - all output some sort of error.
In case it's important, I'm doing this on a Mac.
EDIT: This is a shorter version of the code I previously had in my answer...
If you'd like to read your data file directly, without having to preprocess it first as dstibbe suggested, the following should work:
fid = fopen('datafile.txt','rt');
data = textscan(fid,'%s %s %s %s %s %s %s %s','Delimiter','\n');
fclose(fid);
data = [datenum(data{1}) cellfun(#str2double,[data{2:end}])]';
The above code places each set of 8 values into an 8-by-N matrix, with N being the number of 8 line sets in the data file. The date is converted to a serial date number so that it can be included with the other double-precision values in the matrix. The following functions (used in the above code) may be of interest: TEXTSCAN, DATENUM, CELLFUN, STR2DOUBLE.
I propose yet another solution. This one is the shortest in MATLAB code. First using sed, we format the file as a CSV file (comma seperated, with each record on one line):
cat a.dat | sed -e 's/,//g ; s/[ \t]*$/,/g' -e '0~8 s/^\(.*\),$/\1\n/' |
sed -e :a -e '/,$/N; s/,\n/,/; ta' -e '/^$/d' > file.csv
Explanation: First we get rid of the thousands comma seperator, and trim spaces at the end of each line adding a comma. But then we remove that ending comma for each 8th line. Finally we join the lines and remove empty ones.
The output will look like this:
1999-01-04,1100.00,1060.00,1092.50,0,6225,1336605,37
1999-01-05,1122.50,1087.50,1122.50,0,3250,712175,14
Next in MATLAB, we simply use textscan to read each line, with the first field as a string (to be converted to num), and the rest as numbers:
fid = fopen('file.csv', 'rt');
a = textscan(fid, '%s %f %f %f %f %f %f %f', 'Delimiter',',', 'CollectOutput',1);
fclose(fid);
M = [datenum(a{1}) a{2}]
and the resulting matrix M is:
730124 1100 1060 1092.5 0 6225 1336605 37
730125 1122.5 1087.5 1122.5 0 3250 712175 14
Use a script to modify your text file into something that Matlab can read.
eg. make it a matrix:
M = [
1999-01-04
1,100.00
1,060.00
1,092.50
0
6,225
1,336,605; <-- notice the ';'
37
1999-01-05
1,122.50
1,087.50
1,122.50
0
3,250; <-- notice the ';'
712,175
14
...
]
import this into matlab and read the various vectors from the matrix.
Note: my matlab is a bit rusty. Might containt errors.
It isn't entirely clear what form you want the data to be in once you've read it. The code below puts it all in one matrix, with each row representing a group of 8 rows in your text file. You may wish use different variables for different columns, or (if you have access to the Statistics toolbox), use a dataset array.
% Read file as text
text = fileread('c:/data.txt');
% Split by line
x = regexp(text, '\n', 'split');
% Remove commas from numbers
x = regexprep(x, ',', '')
% Number of items per object
n = 8;
% Get dates
index = 1:length(x);
dates = datenum(x(rem(index, n) == 1));
% Get other numbers
nums = str2double(x(rem(index, n) ~= 1));
nums = reshape(nums, (n-1), length(nums)/(n-1))';
% Combine dates and numbers
thedata = [dates nums];
You could also look into the function textscan for alternative ways of solving the problem.
Similar to Richie's. Using str2double to convert the file strings to doubles. This implementation processes line by line instead of breaking the file up with a regular expression. The output is a cell array of individual vectors.
function vectors = readdata(filename)
fid=fopen(filename);
tline = fgetl(fid);
counter = 0;
vectors = cell(7,1);
while ischar(tline)
disp(tline)
if counter > 0
vectors{counter} = [vectors{counter} str2double(tline)];
end
counter = counter + 1
if counter > 7
counter = 0;
end
tline = fgetl(fid);
end
fclose(fid);
This has regular expression checking to make sure your data is formatted well.
fid = fopen('data.txt','rt');
%these will be your 8 value arrays
val1 = [];
val2 = [];
val3 = [];
val4 = [];
val5 = [];
val6 = [];
val7 = [];
val8 = [];
linenum = 0; % line number in file
valnum = 0; % number of value (1-8)
while 1
line = fgetl(fid);
linenum = linenum+1;
if valnum == 8
valnum = 1;
else
valnum = valnum+1;
end
%-- if reached end of file, end
if isempty(line) | line == -1
fclose(fid);
break;
end
switch valnum
case 1
pat = '(?\d{4})-(?\d{2})-(?\d{2})'; % val1 (e.g. 1999-01-04)
case 2
pat = '(?\d*[,]*\d*[,]*\d*[.]\d{2})'; % val2 (e.g. 1,100.00) [valid up to 1billion-1]
case 3
pat = '(?\d*[,]*\d*[,]*\d*[.]\d{2})'; % val3 (e.g. 1,060.00) [valid up to 1billion-1]
case 4
pat = '(?\d*[,]*\d*[,]*\d*[.]\d{2})'; % val4 (e.g. 1,092.50) [valid up to 1billion-1]
case 5
pat = '(?\d+)'; % val5 (e.g. 0)
case 6
pat = '(?\d*[,]*\d*[,]*\d+)'; % val6 (e.g. 6,225) [valid up to 1billion-1]
case 7
pat = '(?\d*[,]*\d*[,]*\d+)'; % val7 (e.g. 1,336,605) [valid up to 1billion-1]
case 8
pat = '(?\d+)'; % val8 (e.g. 37)
otherwise
error('bad linenum')
end
l = regexp(line,pat,'names'); % l is for line
if length(l) == 1 % match
if valnum == 1
serialtime = datenum(str2num(l.yr),str2num(l.mo),str2num(l.dy)); % convert to matlab serial date
val1 = [val1;serialtime];
else
this_val = strrep(l.val,',',''); % strip out comma and convert to number
eval(['val',num2str(valnum),' = [val',num2str(valnum),';',this_val,'];']) % save this value into appropriate array
end
else
warning(['line number ',num2str(linenum),' skipped! [didnt pass regexp]: ',line]);
end
end