Чтение нерегулярных текстовых файлов с помощью MATLAB - PullRequest
0 голосов
/ 14 октября 2019

Короче говоря, у меня болит голова на нескольких языках, чтобы прочитать текстовый файл (ссылка ниже). Мой самый знакомый язык - MATLAB, поэтому по этой причине я использую его в этом примере. Я нашел способ прочитать этот файл через ~ 5 минут, но, учитывая, что вскоре у меня будут тонны и тонны данных с моего прибора, поскольку он измеряет весь день каждые 30 секунд, это просто невозможно.

Я ищу способ быстро прочитать эти нерегулярные текстовые файлы, чтобы в дальнейшем я мог их выбить с меньшими временными затратами.

Точные данные можно найти по этой ссылке:

http://lb3.pandonia.net/BostonMA/Pandora107s1/L0/Pandora107s1_BostonMA_20190814_L0.txt.bz2

Я использую функцию "readtable" в matlab, и я достиг конечного продукта, который хочу, но я хочу увеличить скорость

Нижемой код!

clearvars -except pan day1; % Clearing all variables except for the day and instrument variables.
close all;
clc;
pan_mat = [107 139 155 153]; % Matrix of pandora numbers for file-choosing 
reasons.

pan = pan_mat(pan); % pandora number I'm choosing
pan = num2str(pan); % Turning Pandora number into a string.
%pan = '107'
pandora = strcat('C:\Users\tadams15\Desktop\Folders\Counts\Pandora_Dta\',pan) 
% string that designates file location



%date = '90919'

month = '09'; % Month
day2 = strcat('0',num2str(day1)) % Creating a day name for the figure I ultimately produce

cd(pandora)
d2 = strcat('2019',num2str(month),num2str(day2)); % The final date variable 
for the figure I produce
%file_pan = 'Pandora107s1_BostonMA_20190909_L0';
file_pan = strcat('Pandora',pan,'s1_BostonMA_',d2,'_L0'); % File name string

%Try reading it in line by line?
% Load in as a string and then convert the lines you want as numbers into
% number. 
delimiterIn = '\t';
headerlinesIn = 41;
A = readtable(file_pan,'HeaderLines', 41, 'Delimiter', '\t'); %Reading the 
file as a table
A = table2cell(A); % Converting file to a cell
A = regexp(A, ' ', 'split'); % converting cell to a structure matrix.

%%
A= array2table(A); % Converting Structure matrix back to table
row_num = 0;
pan_mat_2 = zeros(2359,4126);
datetime_mat = zeros(2359,2);
blank = 0;

%% Converting data to proper matrices
[length width] = size(A);

% The matrix below is going through "A" and writing from it to a new
% matrix, "pan_mat_2" which is my final product as well as singling out the
% rows that contain non-number variables I'd like to keep and adding them
% later.
tic
%flag1
for i = 1:length; % Make second number the length of the table, A
    blank = 0;
    b = table2array(A{i,1});
    [rows, columns] = size(b);
    if columns > 4120 && columns < 4140
       row_num = row_num + 1;
       blank = regexp(b(2), 'T', 'split');
       blank2 = regexp(blank{1,1}(2), 'Z', 'split');
       datetime_mat(row_num,1) = str2double(blank{1,1}(1));
       datetime_mat(row_num,2) = str2double(blank2{1,1}(1));
        for j = 1:4126;
            pan_mat_2(row_num,j) = str2double(b(j));
        end
    end
end
toc
%flag2

Короче говоря, я уже получаю желаемый результат, но часть кода, где я пишу в новый массив "flag 1" - "flag 2", принимаетпримерно 222 секунды, в то время как весь код занимает всего около 248 секунд. Я хотел бы найти лучший способ для создания данных там, чем записать их в новый массив и занять много времени.

Есть предложения?

Ответы [ 2 ]

1 голос
/ 16 октября 2019

Примечание:

Существует немало улучшений, которые вы можете сделать для скорости, но есть и исправления. Вы предварительно выделяете конечную выходную переменную с жестко закодированными значениями:

pan_mat_2 = zeros(2359,4126);

Но позже вы заполняете ее в цикле, который запускает for i = 1:length.

length - полное количество выбранных строкиз файла. В вашем примере файла есть только 784 строк. Таким образом, даже если бы все ваши строки были действительны (нормально для анализа), вы бы заполняли только первые 784 строк из всех 2359 строк, которые вы выделили в своем pan_mat_2. На практике этот файл содержит только 400 допустимых строк данных, поэтому ваш pan_mat_2 может быть определенно меньше.

Я знаю, что вы не могли знать, что перед анализом их было проанализировано только 400 строк, но вы знали изначало, что у вас была только 784 строка для разбора (у вас была информация в переменной length). Таким образом, в случае, подобном этому, предварительно выделите 784 и только позже отбросьте пустые строки.

К счастью, предлагаемое мной решение не требует предварительного выделения большего, чем сброс. Матрицы будут иметь правильный размер с самого начала.


Код:

%%
file_pan = 'Pandora107s1_BostonMA_20190814_L0.txt' ;
delimiterIn = '\t';
headerlinesIn = 41;
A = readtable(file_pan,'HeaderLines', 41, 'Delimiter', '\t'); %Reading the file as a table
A = table2cell(A); % Converting file to a cell
A = regexp(A, ' ', 'split'); % converting cell to a structure matrix.

%% Remove lines which won't be parsed
% Count the number of elements in each line
nelem = cell2mat( cellfun( @size , A  ,'UniformOutput',0) ) ;
nelem(:,1) = [] ;
% find which lines does not have enough elements to be parsed
idxLine2Remove = ~(nelem > 4120 & nelem < 4140) ;
% remove them from the data set
A(idxLine2Remove) = [] ;

%% Remove nesting in cell array
nLinesToParse = size(A,1) ;
A = reshape( [A{:}] , [], nLinesToParse ).' ;
% now you have a cell array of size [400x4126] cells

%% Now separate the columns with different data type
% Column 1          => [String] identifier
% Column 2          => Timestamp
% Column 3 to 4125  => Numeric values
% Column 4126       => empty cell created during the 'split' operation above
%                      because of a trailing space character.
LineIDs    = A(:,1) ;
TimeStamps = A(:,2) ;
Data       = A(:,3:end-1)  ; % fetch to "end-1" to discard last empty column

%% now extract the values

% You could do that directly:
%     pan_mat = str2double(Data) ;

% but this takes a long time. A much computationnaly faster way (even if it
% uses more complex code) would be:
dat = strjoin(Data) ;                                       % create a single long string made of all the strings in all the cells
nums = textscan( dat , '%f' , Inf ) ;                       % call textscan on it (way faster than str2double() )
pan_mat = reshape( cell2mat( nums ) , nLinesToParse ,[] ) ; % reshape to original dimensions

%% timestamps
% convert to character array
strTimeStamps = char(TimeStamps) ;
% convert to matlab own datetime numbering. This will be a lot faster if
% you have operations to do on the time stamps later
ts = datenum(strTimeStamps,'yyyymmddTHHMMSSZ') ;


%% If you really want them the way you had it in your example
strTimeStamps(:,9)   = ' ' ; % replace 'T' with ' '
strTimeStamps(:,end) = ' ' ; % replace 'Z' characters with ' '
%then same again, merge into a long string, parse then reshape accordingly
strdate = reshape(strTimeStamps.',1,[]) ;
tmp = textscan( strdate , '%d' , Inf ) ;
datetime_mat = reshape( double(cell2mat(tmp)),2,[]).' ;

Производительность:

profiler pic

Как вы видите на моем компьютере, ваш исходный код выполняется ~ 102 секунды, причем 80% этого (81 с) затрачивается на вызовфункция str2double() 3,302,400 раз!

Мое решение, работающее с тем же входным файлом, занимает ~ 5,5 секунд, причем половина времени уходит на вызов strjoin() 3 раза.

КогдаВы читаете приведенный выше код, пытаетесь понять, как я ограничил повторение вызова функции в длинных циклах, пытаясь сделать все как можно более векторизованным.

0 голосов
/ 16 октября 2019

Используя profiler , вы можете видеть, что вы вызываете str2double 3302400 раз за сеанс, который занимает около 80% от общего времени на моем компьютере. Теперь это неоптимально, так как каждый раз, когда вы переводите только 1 значение и, насколько идет ваш код, вам больше не нужны значения в виде строки. Я добавил это под вашим исходным кодом:

row_num = 0;
pan_mat_2_b = cell(2359,4126);
datetime_mat_b = cell(2359,2);%not zeros
blank = 0;

tic
%flag1
for i = 1:length % Make second number the length of the table, A
    blank = 0;
    b = table2array(A{i,1});
    [rows, columns] = size(b);
    if columns > 4120 && columns < 4140
       row_num = row_num + 1;
       blank = regexp(b(2), 'T', 'split');
       blank2 = regexp(blank{1,1}(2), 'Z', 'split');
       %datetime_mat(row_num,1) = str2double(blank{1,1}(1));
       %datetime_mat(row_num,2) = str2double(blank2{1,1}(1));
       datetime_mat_b(row_num,1) = blank{1,1}(1);
       datetime_mat_b(row_num,2) = blank2{1,1}(1);
       pan_mat_2_b(row_num,:) = b;
%      for j = 1:4126
%           pan_mat_2(row_num,j) = str2double(b(j));
%        end
    end
end

datetime_mat_b = datetime_mat_b(~all(cellfun('isempty',datetime_mat_b),2),:);
pan_mat_2_b=pan_mat_2_b(~all(cellfun('isempty',pan_mat_2_b),2),:);

datetime_mat_b=str2double(string(datetime_mat_b));
pan_mat_2_b=str2double(pan_mat_2_b);
toc

Все еще не отлично, но лучше. Если вы хотите еще больше ускорить это, я рекомендую вам взглянуть на часть readtable. Как вы можете сэкономить довольно много времени, если вы начнете с чтения в формате удваивается с самого начала

...