Compare char data in a cell {'x'} to a character 'x'

21 views (last 30 days)
I want to find what's in my table that's in a category column 1
If you read through you will see what I tries and how I finally figured it out. I'm posing the question because I can't be the only person that wanted to do this simple (but not clearly and simply documented)
Starting with a text file:
HEADER that is ignored
R 0 5
L 5 0
Set up how I want the table (not that MATLAB will pay any attention)
app.StimInputTable.Data = table('Size',[4 3],...
'VariableTypes',{'categorical','uint8','uint8'});
What did I get? Got what I asked for
K>> class (app.StimInputTable.Data.Var1)
ans =
'categorical'
K>> class (app.StimInputTable.Data.Var2)
ans =
'uint8'
K>> class (app.StimInputTable.Data.Var3)
ans =
'uint8'
Read in the file
app.StimInputTable.Data = readtable(infile);
MATLAB changes all the types
K>> class (app.StimInputTable.Data.Var1)
ans =
'cell'
K>> class (app.StimInputTable.Data.Var2)
ans =
'double'
K>> class (app.StimInputTable.Data.Var3)
ans =
'double'
I can redo the .. table(... VariableTypes), but then it tosses all my category data instead of converting
So here's my table, I can deal with the cells
K>> app.StimInputTable.Data
ans =
2×3 table
Var1 Var2 Var3
_____ ____ ____
{'L'} 5 0
{'R'} 0 5
So the char is considered a cell, ok I've seen a warning about how chars (or char vectors) get put into cells, but my users are not going to type "R" 5 0 etc these files already exist.
K>> app.StimInputTable.Data(jj,1)
ans =
table
Var1
{'L'}
Try to pull it out and I get another table, not the cell
OK so I pull it this way
K>> app.StimInputTable.Data{jj,1}
ans =
1×1 cell array
{'L'}
Now I have a more simple 1x1 cell array, now to the character out of the cell or compare cell to cell
How about I compare it to a cell with the char in it
K>> {'L'}
ans =
1×1 cell array
{'L'}
That looks good, same types, lets try it
K>> app.StimInputTable.Data{jj,1} == {'L'}
Operator '==' is not supported for operands of type 'cell'.
So that didn't work, so I'll try
K>> cl = app.StimInputTable.Data{jj,1}
cl =
1×1 cell array
{'L'}
Get the char out of the cell
K>> cl{1,1}
ans =
'L'
K>> cl{1,1}== 'L'
ans =
logical
1
Finally!
cl = app.StimInputTable.Data{jj,1}
if cl{1,1}== 'L'
...
end
Is this really what I have to do? It took way too long to figure out. I never could find Help on how to extract the single cell of data out of a table. I have R and L as catagories when I use them later but can't read them in as such initing the table size and variabletypes gets ignored by the read()
Wait a moment
app.StimInputTable.Data{j,1} % This gives a cell as above, but
app.StimInputTable.Data.Var1{j} % is going to work? Where is that documented?
ans =
'L'
So now
if app.StimInputTable.Data.Var1{j} == 'L'
Apparently using .Var1{i} is not the same as indexing with {j}
This works but where will you find it documented? Good luck
  2 Comments
Stephen23
Stephen23 on 26 Sep 2024
Edited: Stephen23 on 26 Sep 2024
"MATLAB changes all the types"
No, MATLAB does not change the data types of your table.
What actually happens is that you completely replace (i.e. discard) one table by replacing it with another completely different table. If you want to keep the original table then you will need to use e.g. indexing into it.
"Is this really what I have to do?"
The simpler and more robust approach is to use the text comparison commands (which accept various types of text) e.g.:
if strcmp('L',app.StimInputTable.Data{jj,1})
"Compare char data in a cell {'x'} to a character 'x'"
See the section entitled "Compare" here, it lists functions that are used for comparing text:
Low-level functions (e.g. EQ, indexing, arithmetic) operate on characters because this is useful for people who need character manipulations. But if you are not doing character bashing, then you should be using the text functions to do comparisons.
"I've seen a warning about how chars (or char vectors) get put into cells, but my users are not going to type "R" 5 0 etc these files already exist."
Adding double quotes would not make any difference (by CSV file convention, double quotes are not imported literally, they simply indicate that a field contains text). What does work is specifying the text type:
T = readtable('mytest.txt', 'TextType','string')
T = 2x3 table
Var1 Var2 Var3 ____ ____ ____ "R" 0 5 "L" 5 0
"Apparently using .Var1{i} is not the same as indexing with {j}"
Of course not: both the dot notation and the curly braces return the content of the table, which is a cell array (because that is what your table contains). So these are equivalent:
app.StimInputTable.Data{:,1}
app.StimInputTable.Data.Var1
You then added some more curly brace indexing onto only one of your examples, thus making it a comparison of apples and oranges. But there is nothing stopping you from indexing into that cell array with both examples, probably something like this:
app.StimInputTable.Data{j,1}{1}
app.StimInputTable.Data.Var1{j}
"This works but where will you find it documented"
"Good luck"
Thanks.
Gavin
Gavin on 27 Sep 2024
Thanks for the help @Stephen23
Plenty of choices
I've tried "double" indexing similar to this and it didn't work.
app.StimInputTable.Data{j,1}{1}
So ti works with {}, but maybe not () I don't remember the exact thing I tried.

Sign in to comment.

Accepted Answer

Voss
Voss on 26 Sep 2024
1.
app.StimInputTable.Data = readtable(infile);
That overwrites completely whatever was in app.StimInputTable.Data before (as you found out), just like if you do
x = '';
x = 5;
the fact that x was a character array before assigning to it a numeric value makes no difference.
If you want to specify the variable types of a table created by readtable, use file import options as in
opts = detectImportOptions(infile);
opts = setvartype(opts,{'categorical','uint8','uint8'});
app.StimInputTable.Data = readtable(infile,opts);
2. Use isequal instead of == to compare cell arrays.
3. See Accessing Data in Tables for documentation about syntaxes used with tables.
  2 Comments
Stephen23
Stephen23 on 26 Sep 2024
"Use isequal instead of == to compare cell arrays."
Possible, but using the actual text comparison functions would be better for comparing text.
Gavin
Gavin on 27 Sep 2024
Thanks, good short answer. Too bad I can't accept all 3

Sign in to comment.

More Answers (2)

Paul
Paul on 26 Sep 2024
Edited: Paul on 26 Sep 2024
Create the sample .txt file
filetext = ["HEADER that is ignored";"R 0 5";"L 5 0"];
writelines(filetext,"test.txt");
type test.txt
HEADER that is ignored R 0 5 L 5 0
This command creates a table of four rows and three variables
app.StimInputTable.Data = table('Size',[4 3],...
'VariableTypes',{'categorical','uint8','uint8'});
app.StimInputTable.Data
ans = 4x3 table
Var1 Var2 Var3 ___________ ____ ____ <undefined> 0 0 <undefined> 0 0 <undefined> 0 0 <undefined> 0 0
This command simply overwrites whatever is on the left hand side, just like what happens with any other assignment statement. There should be no surprise here. I guess readtable() is smart enough to skip the first line. Absent any other direction, the data is read in with the default types, all as documented at @doc:readtable as far as I can tell.
app.StimInputTable.Data = readtable('test.txt');
app.StimInputTable.Data
ans = 2x3 table
Var1 Var2 Var3 _____ ____ ____ {'R'} 0 5 {'L'} 5 0
Parentheses indexing into a table returns a table, exactly as documented at Access Data in Tables
jj = 2;
app.StimInputTable.Data(jj,1)
ans = table
Var1 _____ {'L'}
Brace indexing into a table returns the contents of the table, exactly as documented at Access Data in Tables
app.StimInputTable.Data{jj,1}
ans = 1x1 cell array
{'L'}
For this simple case the result can be compared to 'L' like so, no need to create the temporary variable.
app.StimInputTable.Data{jj,1}{1} == 'L'
ans = logical
1
As noted, this line returns a scalar cell
app.StimInputTable.Data{jj,1} % This gives a cell as above, but
ans = 1x1 cell array
{'L'}
The first column of the table can be referenced as (see Access Data in Tables)
app.StimInputTable.Data.Var1
ans = 2x1 cell array
{'R'} {'L'}
which is a cell array. The jj'th element of that cell array is extracted with brace indexing in the usual way
app.StimInputTable.Data.Var1{jj}
ans = 'L'
Everything seems to be working as expected and documented.
  1 Comment
Gavin
Gavin on 27 Sep 2024
Thanks, good answer all. Sorry for the rant.
Too bad I can't accept all 3

Sign in to comment.


Steven Lord
Steven Lord on 26 Sep 2024
You've got a lot in here. I'm going to step through certain parts.
Let's start off with this comment (formatting added, your words are generally in italics):
Set up how I want the table (not that MATLAB will pay any attention)
app.StimInputTable.Data = table('Size',[4 3],...
'VariableTypes',{'categorical','uint8','uint8'});
It did "pay attention" to what you told it to do. Those class calls you made show that. Now the next thing you did:
Read in the file
app.StimInputTable.Data = readtable(infile);
MATLAB changes all the types
Not exactly, though the effect that it had did make the types of the variables in app.StimInputTable.Data different. It didn't change the types; it replaced the entire array that you had in that field of that property, exactly as if you'd typed:
x = single(1i)
x = 1:10
If you ran that example, the complex single scalar value that the first command had assigned to x would be thrown away and the real double vector value that the second command assigned to the name x would take its place. What you wrote is exactly this same idea, just with calls to table and readtable instead of single(1i) and 1:10.
I can redo the .. table(... VariableTypes), but then it tosses all my category data instead of converting
I recommend using detectImportOptions on the file, modifying it if the options that MATLAB detected when it inspected the file don't match what you want, and passing those options into readtable as shown in the "Read Subset of Text File Using Import Options" example on that documentation page.
So here's my table, I can deal with the cells
K>> app.StimInputTable.Data
ans =
2×3 table
Var1 Var2 Var3
_____ ____ ____
{'L'} 5 0
{'R'} 0 5
So the char is considered a cell
No. Var1 in this case is a cell array, each cell of which contains a char array. Think of a cell array like a dresser drawer; each drawer can hold socks, or shirts, or underwear, etc. but the drawers aren't themselves socks, shirts, or underwear.
The metaphor breaks down a little since a cell in a cell array can itself contain a cell arrray, the cells of which can contain other stuff.
ok I've seen a warning about how chars (or char vectors) get put into cells, but my users are not going to type "R" 5 0 etc these files already exist.
K>> app.StimInputTable.Data(jj,1)
ans =
table
Var1
{'L'}
Try to pull it out and I get another table, not the cell
That's correct. Indexing into a table like that using parentheses gets you another table. This is row 6 in the table summarizing table accessing expressions in the last section on this documentation page.
OK so I pull it this way
K>> app.StimInputTable.Data{jj,1}
ans =
1×1 cell array
{'L'}
Correct, indexing into a table with curly braces extracts the contents of the table variable(s). That's row 3 in that summary table.
Now I have a more simple 1x1 cell array, now to the character out of the cell or compare cell to cell
How about I compare it to a cell with the char in it
K>> {'L'}
ans =
1×1 cell array
{'L'}
That looks good, same types, lets try it
K>> app.StimInputTable.Data{jj,1} == {'L'}
Operator '==' is not supported for operands of type 'cell'.
No, that's not going to work. A cell array is a container for data and so not something you're usually going to want to compare with data.
So that didn't work, so I'll try
K>> cl = app.StimInputTable.Data{jj,1}
cl =
1×1 cell array
{'L'}
Get the char out of the cell
K>> cl{1,1}
ans =
'L'
K>> cl{1,1}== 'L'
ans =
logical
1
Finally!
I'd recommend, unless you have a need to operate with cell arrays of char vectors, that you convert the first variable of your data to a string array and use string comparisons. The == operator is defined for strings, and you can compare string arrays and char arrays using == just fine.
app.StimInputTable.Data.Var1 = string(app.StimInputTable.Data.Var1)
Alternately, if you need to use cell arrays containing char arrays, don't use == for comparison. Use something like matches or the other string manipulation functions instead.
matches({'L'}, 'L') % returns true
matches({'L'}, "L") % also true
Looking at your code:
cl = app.StimInputTable.Data{jj,1}
if cl{1,1}== 'L'
...
end
Is this really what I have to do?
No, and in fact this is not going to work in certain (not uncommon) circumstances. As you've written your code, if cl{1, 1} is the 2-element char array 'LL' MATLAB will enter your if statement body. In addition, if the two char arrays you're comparing have different number of characters, and neither is scalar, like 'Hello'=='Goodbye', MATLAB will error just as though you'd asked it if (1:4) == (1:5).
It took way too long to figure out. I never could find Help on how to extract the single cell of data out of a table. I have R and L as catagories when I use them later but can't read them in as such initing the table size and variabletypes gets ignored by the read()
I recommend using detectImportOptions with readtable to make sure MATLAB imports the data the way you want it, using string arrays if possible, and if you do need to work with char arrays or cell arrays containing char arrays use the string comparison functions listed on this documentation page. Hopefully that page I linked to showing the summary of different ways to access data in tables will be useful to you as well.
And if figuring out how to set up the import options programmatically is too time consuming, use the Import Tool to set up your data file how you want it, then generate code by clicking the little down pointing triangle on the Import Selection green checkmark icon and copy and paste that code into your app.
Wait a moment
app.StimInputTable.Data{j,1} % This gives a cell as above, but
app.StimInputTable.Data.Var1{j} % is going to work? Where is that documented?
That summary table on the documentation page I linked above that shows the various ways to access data in a table. This is kind of a variant on row 2 in that table, and partly just chaining of dot indexing (to get a cell array) and curly brace indexing into that cell array. It's effectively the same as this operation on a struct whose field contains a cell.
>> s = struct;
>> s.data = {1 4 9}
>> s.data{2}
ans =
'L'
So now
if app.StimInputTable.Data.Var1{j} == 'L'
Apparently using .Var1{i} is not the same as indexing with {j}
This works but where will you find it documented? Good luck
See that page with the summary table. It goes into detail about how to work with data in tables.
  1 Comment
Gavin
Gavin on 27 Sep 2024
Thanks, good answer all. Sorry for the rant.
Too bad I can't accept all 3

Sign in to comment.

Categories

Find more on Tables in Help Center and File Exchange

Products


Release

R2023b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!