How do I apply unique names to structs created in a for loop?
Show older comments
I have this script and by the end of it I have variables in the workspace containing the quantity and names of vehicle performance modes to be tested, and I have the folder loacations of the data to be analyzed.
%% Initialize
close all
clear
clc
%% Select path and generate list of modes
% Prompt user to identify top level directory for the vehicle to be
% analyzed.
vehiclePath = uigetdir('','Select Vehicle Data Directory');
% Extract sub folder names which should identify the number and name of
% various performance modes tested in the selected vehicle. Ignore ., ..,
% and default folder names.
pathContents = dir(vehiclePath);
folders = pathContents(~ismember({pathContents.name},{'.','..','AWD-insert mode name with no spaces-'}));
dirFlags = [folders.isdir];
subFolders = folders(dirFlags);
modeList = {subFolders.name};
clearvars pathContents folders dirFlags
%% List Generation
% List of AWD hardware variants
variantList = {'Booster','Twinster'};
% List of all standardized test maneuvers used for validation. This list
% matches the sub folders contained within each performance mode (second
% level folders named in 'modeList' above).
testList = {'VVT_001 High Mu Path Deviation',...
'VVT_002 High Mu CRAM',...
'VVT_003 High Mu Acceleration',...
'VVT_004 Torque Steer Evaluation',...
'VVT_005 High Mu Sine Steer',...
'VVT_006 Low Mu Sine Steer',...
'VVT_007 High Mu AIT 90deg SWA',...
'VVT_008 High Mu AIT 45deg SWA',...
'VVT_009 Low Mu Acceleration',...
'VVT_010 Low Mu 50kph Tip-In',...
'VVT_011 Low Mu 100kph Tip-In',...
'VVT_012 AWD Response Time',...
'VVT_013 Level Mu Split',...
'VVT_014 Level Mu Step',...
'VVT_015 Mu Split 10pct Grade',...
'VVT_016 Mu Split 15pct Grade',...
'VVT_017 Mu Split 20pct Grade',...
'VVT_018 Mu Split 30pct Grade',...
'VVT_019 Micro Mu Split 50pct Tip-In',...
'VVT_020 Micro Mu Split WOT Tip-In',...
'VVT_021 Low Mu CRAM',...
'VVT_022 Small Ice AIT',...
'VVT_023 Small Ice DTIT',...
'VVT_024 Small Snow AIT',...
'VVT_025 Small Snow DTIT',...
'VVT_026 Large Ice AIT',...
'VVT_027 Large Ice DTIT',...
'VVT_028 Large Snow AIT',...
'VVT_029 Large Snow DTIT'};
%% Variant Selection
% Prompts user to identify which type of AWD system was equipped for
% testing. This impacts calculations and plots which execute in other
% scripts.
[variant,TF] = listdlg('ListString',variantList,'ListSize',[220,100],'Name','Select Variant','SelectionMode','Single');
if TF == 0
Err = errordlg('No Variant Selected','Analysis Canceled');
set(Err,'Position',[600 600 200 60]);
return
else
end
drivelineConfig = variantList{variant};
clearvars TF
%% User Selection
% Prompts user to idenfity which test is to be analyzed.
[test,tf] = listdlg('ListString',testList,'ListSize',[300 600],'Name','Select Comparison Maneuver','SelectionMode','Single');
if tf == 0
err = errordlg('No Analysis Selected','Analysis Canceled');
set(err,'Position',[600 600 200 60]);
return
else
end
selectedTest = testList{test};
clearvars tf
%% Create analysis path
analysisPath = cell(2,1);
for ii = 1:length(modeList)
analysisPath(ii) = fullfile(vehiclePath,modeList(ii),selectedTest,filesep);
end
clearvars ii
%% How to use analysisPath and modeList to name the structs, locate the data, and populate the structs in the workspace?
modeList is the quantity and names of the performance modes and these are the names I want to use to define my structs. In this case there are only two, "Fwd" and "Awd". In other vehicle applications there will be more. Other vehicles might have "AwdSport", "AwdOffroad", "AwdSand", "AwdMud", etc.
analysisPath is the file path location to where I need to grab the .mat files and populate the structs.
I'm struggling to sort out the next section of the script. I know I need to run this in a loop, but I can't figure out how to run a loop and output a unique struct name pulled from the values in modeList. I'm also struggling to access the specific data files that I need. This works to access the files contained within the paths defined by analysisPath, but it lists every file and not just the .mat files I'm looking for. Struggling to find the right search terms to find the results I need to push past this.
dir(analysisPath{i})
This is my older script for loading .mat files into a struct. Initialy my scripting was focused around only comparing Fwd and Awd data and didn't allow for additional Awd modes. In this case I could simply prompt the user to select specific data files and then load the Fwd data into a struct named D2 (Data 2WD). I used the same script with F4/P4/D4/etc. to load and store data for 4WD. I know I need to reuse the loop to get the data from the mat files into the structs. I'm not sure how to loop this such that I can populate the struct and then name it for the mode folder from which it populated data. I'd like it to be automated so the naming of folders by users is a bit more flexible. I'm sure it can be done and likely requires a couple nested for loops and maybe breaks and returns, but I'm having a hard time visualizing it before blindly starting to type.
[F2,P2] = uigetfile('*.mat','Select 2WD Data File','MultiSelect','on');
if isnumeric(F2)
error('User quit')
elseif ischar(F2)
F2 = {F2};
end
D2 = struct('filename',F2);
for ii = 1:numel(F2)
Tmp2 = load(fullfile(P2,F2{ii}));
L2 = [{'Time'};Tmp2.Data_Labels(1:end-1)]; % fix "Time" column mismatch
for jj = 1:numel(L2)
D2(ii).(L2{jj}) = Tmp2.Data(:,jj);
end
end
18 Comments
Joel Handy
on 19 Jul 2019
Are you just trying to dynamically name variables? Specifically name variables according to a folder name? Its generally a bad idea to dynamically name variables and folder names do not have to be valid variable names.
Is there a reason you can't have a test_ID field in these structures to capture this information?
Also you can pass in a filter string to dir, e.g.
dir(analysisPath{i}, '*.mat')
Adam Danz
on 19 Jul 2019
I didn't dig in to your code too deeply but if you're intentions are to dynamically name structures in a loop, you'd be better off rethinking the organization of your data (as mentioned by Joel). A better approach would be to store the structures in a structure array if all of them have the same field names or within a cell array otherwise.
This is not valid MATLAB syntax:
dir(analysisPath{i},'*.mat')
because the dir function accepts a maximum of one input argument, not two (or more). You can use fullfile to create that argument from multiple parts:
dir(fullfile(analysisPath{i},'*.mat'))
As Joel Handy and Adam Danz have already mentioned, storing any kind of meta-data (e.g. folder names, test subject names, indices, etc.) in variable name is a bad way to write code (because you force yourself into writing slow, complex, buggy, obfuscated code to try and access it). Meta-data is data, and data should be stored in a variable, not in a variable's name.
As Adam Danz mentioned, a structure array is possibly a good choice for your data. You can even use the one returned by dir and add new fields to it for the imported data:
S = dir(...);
for k = 1:numel(S)
S(k).data = import your data here
end
and then you will have all of your imported file data and meta-data together in one structure:
Scooby921
on 22 Jul 2019
Guillaume
on 22 Jul 2019
I know any actions to be performed on the "FWD" data would find the variables and values in a struct named "FWD". Actions to be performed on the "AWD" data would find the varialbes and values in a struct named "AWD"
This is dynamic variable names and probably not a good idea.
It sounds to me (I haven't read the whole discussion in details so may be wrong), that you're trying to store a database of vehicle tests. In my opinion, in modern matlab the best way to store this sort of things is with a table. This is the closest matlab has to a proper database.
So for example, you could have a table whose 1st variable would be the dataset type (FWD, AWD, AWDOFFROAD, etc.), the 2nd variable the source folder, and so on.
Scooby921
on 22 Jul 2019
Guillaume
on 22 Jul 2019
Ok, your folder structure is clear and make sense. But what has the folder structure got to do with variable names?
Scooby921
on 22 Jul 2019
The advice that you are being given in this discussion is valuable, you really should consider it.
"I don't want to store the data in the variable name."
That directly contradicts what you write just a few sentences later: "I was hoping I could pull the names from this cell array and use them to name / define the structs"
Names are meta-data. Meta-data is data. So you are trying to store data in variable names.
The complexity of your folder structure is not really relevant: you can loop over them, or use a recursive function, or use dir, or whatever works for you. At some point you will need to flatten your data (or do whatever to process it), and for that a table seems to be very well suited, just as Guillaume explained. A table makes it trivial to store the imported data and the meta-data (folder names, etc.) together and makes it easy to check which meta-data (folder names, etc.) are present (using string matching, regular expressions, etc.) and therefore run your different analyses (depending on the meta-data) on the imported data. Really, that is exactly what tables are good at:
https://www.mathworks.com/help/matlab/matlab_prog/split-table-data-variables-and-apply-functions.html
"I still am struggling to figure out if I can and then how to give the structs different names"
It is certainly possible to do that, but it would not be a good approach to writing code (unless you like forcing yourself into writing slow, complex, buggy code that is hard to debug). Using a table would likely be much simpler.
"Realistically I don't NEED to have my structs named for the modes. I don't NEED to pull the names from the modeList cell array. I can simply call them A, B, C, D, etc.."
If you have a known, fixed number of structures then there is nothing stopping you from hard-coding them before the loops and then accessing them inside the loops. It would be reasonably efficient, if rather verbose code.
"Is there a way to use the same loop to populate multiple structs?"
Of course: define multiple structures before the loop, and then inside the loop use switch or whatever. This would be more complex than just using one table or structure array, but you do seem very determined to do this...
"If a user adds a mode for "AwdSport", then I have a third struct generated..."
This is where your concept falls down: either you can hard-code a fixed number of structures (and easily write efficient, easy to debug, understandable code) or you can dynamically create structures on the fly (and force yourself into writing slow, complex, buggy code). You can't do both.
"I think I'm just getting lost and confused over semantics of words. You are all talking about "storing" data in a variable. I'm thinking of it as "using" the data to name a variable"
Semantics is important, so that we can clearly describe what are the code's requirements, what does code actually do, etc..
"I don't know what I don't know and that leaves me incredibly frustrated at times, and a little anxious all too often since I don't know what to search for to find an answer or don't know how to phrase my questions to best convey the issue."
Agreed: it is frustrating not even knowing what terms to search for! Everyone experiences this, when they start learning something new. The only reliable cure is time: reading documentation, looking at examples, asking questions, etc. then you gain knowledge, not just how to do X, but how to find out information about Y. It just takes time... and we are always happy to help.
PS: you might like to read this too:
Guillaume
on 22 Jul 2019
Looking at the code you use to create your structure, I'd say you could either use a table or a structure. You can easily convert between one and the other anyway. Tables are probably easier to search into and might be more efficient memory-wise (but possibly slower). Using either type of storage, I don't see why your performance mode can't be another field of the structure / another variable of the table.
At the moment, in the field of each element of the structure you're storing column vectors. If you're going to use tables it may be more efficient to flatten that so that each element of the vectors is one row of the table (with duplicated filename and mode). I.e. having this kind of structure:
D2(1).filename = "file1.mat";
D2(1).Mode = "AWD";
D2(1).Property1 = [1;2;3;4];
D2(1).Property2 = [4;5;6;7];
D2(2).filename = "file2.mat";
D2(2).Mode = "FWD";
D2(2).Property1 = [8;9;10;11;12;13];
D2(2).Property2 = [14;15;16;17;18;19];
You'd have a flat table:
Filename Mode Property1 Property2
-------------------------------------------
"file1.mat" "AWD" 1 4
"file1.mat" "AWD" 2 5
"file1.mat" "AWD" 3 6
"file1.mat" "AWD" 4 7
"file2.mat" "FWD" 8 14
"file2.mat" "FWD" 9 15
"file2.mat" "FWD" 10 16
"file2.mat" "FWD" 11 17
"file2.mat" "FWD" 12 18
"file2.mat" "FWD" 13 19
It does store redundant information but it makes it very easy to perform grouped computation (e.g mean of properties per mode). It's just
groupsummary(thetable, 'Mode', 'mean', 3:end);
%or
varfun(@mean, thetable, 'GroupingVariables', 'Mode', 'InputVariables', 3:end)
"Also of concern to me is that my data acquisition tool is inconsistent in its data export structure."
If your meta-data (e.g. sample channel names, units, data format, etc.) changes between tests, then you really need masssage your data into one common format, e.g. change the channel names to a common naming scheme, etc. You could create a "universal data parser", but that is a monster that you probably shouldn't be fighting. So I recommend that you find out how to make your imported data conform as much as possible to a common template (of names, or whatever other meta-data). This does not mean that every file needs to contain exactly the same data: a few missing channels can be handled easily.
"...whether or not I can populate a cell in a table with a vector of data"
It might be possible (in cell array variables/columns), but I recommend simple "scalar" data in each table element (which really makes tables much more usable).
"For a given .mat file all channels will have the same number of data points, but it's going to be an n x 1 vector of numerical values. Highly unlikely that File1 and File2 and Filen are going to have the same number of data points in the vectors."
You could combine a simple non-scalar structure and tables, to get the best of both worlds:
S = dir(...); returns a structure with file meta-data.
for k = 1:N
X = load(...); import mat file data
T = ... massage and convert mat file data to table.
S(k).data = T;
end
This is essentially the same as Guillaume's earlier suggestion. That the different files have different numbers of samples is irrelevant.
"The issue in that linked older question / answer is that the data acquistion tool spits out one .mat file with two variables. One is a cell array with each cell being a different n x 1 vector of data, and the other is a character array with each row being a channel name."
That could easily be converted to a structure using cell2stuct, or a table using cell2table. That does the "linking" for you, and make the "variable" order irrelevant. A table would probably be best.
Scooby921
on 26 Jul 2019
Answers (1)
Joel Handy
on 22 Jul 2019
Edited: Joel Handy
on 22 Jul 2019
Does this help at all? I'm not completely sure I understand the nuances of your question, but If I had a folder structure like you showed in your picture, I might arrange the data something like this. No dynamic variable naming but you can still clearly identify which folders each set came from. You also don't need to worry about folder names being valid matlab variable names.
modeList = {'AWD', 'FWD'};
testList = {'VVT_001 High Mu Path Deviation',...
'VVT_002 High Mu CRAM',...
'VVT_003 High Mu Acceleration',...
'VVT_004 Torque Steer Evaluation',...
'VVT_005 High Mu Sine Steer',...
'VVT_006 Low Mu Sine Steer',...
'VVT_007 High Mu AIT 90deg SWA',...
'VVT_008 High Mu AIT 45deg SWA',...
'VVT_009 Low Mu Acceleration',...
'VVT_010 Low Mu 50kph Tip-In',...
'VVT_011 Low Mu 100kph Tip-In',...
'VVT_012 AWD Response Time',...
'VVT_013 Level Mu Split',...
'VVT_014 Level Mu Step',...
'VVT_015 Mu Split 10pct Grade',...
'VVT_016 Mu Split 15pct Grade',...
'VVT_017 Mu Split 20pct Grade',...
'VVT_018 Mu Split 30pct Grade',...
'VVT_019 Micro Mu Split 50pct Tip-In',...
'VVT_020 Micro Mu Split WOT Tip-In',...
'VVT_021 Low Mu CRAM',...
'VVT_022 Small Ice AIT',...
'VVT_023 Small Ice DTIT',...
'VVT_024 Small Snow AIT',...
'VVT_025 Small Snow DTIT',...
'VVT_026 Large Ice AIT',...
'VVT_027 Large Ice DTIT',...
'VVT_028 Large Snow AIT',...
'VVT_029 Large Snow DTIT'};
testSets = struct('Mode', {}, 'TestID', {}, 'TestData', {})
for mode = modeList
testSets = [testSets struct('Mode', mode{:}, 'TestID', testList, 'TestData', [])];
end
for testIdx = 1:numel(testSets)
% testSets(testIdx).TestData = load(<build up filename based on mode and testID>);
end
Categories
Find more on Multi-Object Trackers in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!