Fit a standard exponential fit to approximate data
50 ビュー (過去 30 日間)
古いコメントを表示

1. For every exponential shaped signal, I want to fit a standard exponential decay fit (I've drawn in black) by joining the possible points in the green line. Also I need the count of such fits in the plot along with the duration of each.
2. I want to find out the slope (I've drawn in violet colour with a question mark) by drawing a line (I've drawn in dark cyan) that connects the starting and ending point of the standard fit. How to do?
Herewith I've attached the Excel sheet and the code that I've tried so far.
clc; close all; clear all;
a=readtable('Data.xlsx');
time=a{2:40000,1};amplitude=a{2:40000,3};
max_peak_values = [];
max_peak_locs = [];
window_size = 1000;
for i = 1:window_size:length(amplitude)
end_index = min(i + window_size - 1, length(amplitude));
segment = amplitude(i:end_index);
[max_peak, loc] = max(segment);
max_peak_values = [max_peak_values; max_peak];
max_peak_locs = [max_peak_locs; loc + i - 1];
end
joined_signal = zeros(size(amplitude));
joined_signal(max_peak_locs) = max_peak_values;
for j = 1:length(max_peak_locs)-1
x = [max_peak_locs(j), max_peak_locs(j+1)];
y = [max_peak_values(j), max_peak_values(j+1)];
interp_x = x(1):x(2);
interp_y = linspace(y(1), y(2), length(interp_x));
joined_signal(interp_x) = interp_y;
end
figure;
plot(time, amplitude, 'b');
hold on; grid on
plot(time(max_peak_locs), max_peak_values, 'ro');
plot(time, joined_signal, 'g');
2 件のコメント
Image Analyst
2025 年 1 月 27 日 18:44
Can you explain how the green line is determined? It looks like you sort of want to fit only the peaks of your data rather than all of the data, however that assumption breaks down in a few places, like around x=0.065 where the green line does not hit a peak, and around 0.04 where it's skipping some peaks.
Can we assume that if there is a run of 0.025 where the values are less than 2, that defines a region that divides total signal into separate exponentially shaped signals? Finding those zones is trivial with thresholding and bwareaopen (to extract only zones of a certain length of longer) and then bwlabel (to give a unique ID number to each flat region.
mask = bwareaopen(signal < 2, minAllowableLength); % minAllowableLength is the minimum number of indexes you'll allow for a seeparation zone.
labeledMask = bwlabel(mask);
What do you need the fits (equation parameters) for? If all you need is the count of signal regions, and the slopes from highest peak to last/final peak, then you don't need to compute a fit to determine just those two things.
And why not fit the exponential curve through the data instead of just the peaks? And if you did that, would you want to exclude those points where the data falls to less than 2, so that you just include the data in the 4-peak regions, not the baseline data?
回答 (3 件)
Sam Chak
2025 年 1 月 27 日 17:40
Hi @Reji G
You can use the approach described by @William Rose. Alternatively, you can use the envelope() function. The following example is a code snippet in which I employed the bi-exponential exp2 model instead of the monoexponential model.
data= importdata('Data.xlsx');
t = data.data(:,1);
y = data.data(:,2:3);
yy = y(:,2);
[mv, idx] = max(yy);
t = t(idx:20001);
yy = yy(idx:20001);
[up, ~] = envelope(yy, 2000, 'peak');
hold on
% plot(t, up, 'linewidth', 1.5)
f = fit(t, up, 'exp2')
plot(f, t, yy), grid on
title('Y2'); xlabel('t')
5 件のコメント
Sam Chak
2025 年 1 月 28 日 14:01
Your observation is sharp. If the OP expects a monotonically decreasing envelope, then some data processing will be required before fitting the curve to the data.
Hi @Reji G
You should split the signal into the left half (first exponential pattern) and the right half (second exponential pattern). You may manually select the desired local maximum points that describe the envelope of the signal. I learned the 'envelope()' approach from @Star Strider's solutions in other threads. You can refer to past questions to find similar problems.
William Rose
2025 年 1 月 28 日 15:09
@Sam Chak fits the envelope() of the signal, above. Which is a smart idea, in my opinion. But the resulting fit includes a small rising exponential. If you find the maximum points with findpeaks(), and fit them with 'exp2', you get two decaying exponentials, which is more like what you want, I think. I don't know exactly why you get different results with envelope() versus the maxes from findpeaks(). envelope() seems more elegant but more opaque. With findpeaks(), I always have to experiment a bit with the options, to find the peaks I want. See my comment to my answer, below.
prabhat kumar sharma
2025 年 1 月 27 日 9:47
Hello Reji,
To fit a standard exponential decay to your data and calculate the slope of the line connecting the start and end points of the fit, you can follow these steps in MATLAB. Since I don't have access to your actual data file (Data.xlsx), I'll provide you with a general approach using your existing code as a base.
- Identify Regions for Exponential Fit: Use the joined_signal to identify segments that resemble exponential decays.
- Fit Exponential Decay: For each identified segment, fit an exponential decay model.
- Calculate Slope: Compute the slope of the line connecting the start and end points of the fitted exponential curve.
clc; close all; clear all;
a = readtable('Data.xlsx');
time = a{2:40000, 1};
amplitude = a{2:40000, 3};
% Parameters
window_size = 1000;
decay_fits = [];
% Find peaks
max_peak_values = [];
max_peak_locs = [];
for i = 1:window_size:length(amplitude)
end_index = min(i + window_size - 1, length(amplitude));
segment = amplitude(i:end_index);
[max_peak, loc] = max(segment);
max_peak_values = [max_peak_values; max_peak];
max_peak_locs = [max_peak_locs; loc + i - 1];
end
% Create joined signal
joined_signal = zeros(size(amplitude));
joined_signal(max_peak_locs) = max_peak_values;
for j = 1:length(max_peak_locs)-1
x = [max_peak_locs(j), max_peak_locs(j+1)];
y = [max_peak_values(j), max_peak_values(j+1)];
interp_x = x(1):x(2);
interp_y = linspace(y(1), y(2), length(interp_x));
joined_signal(interp_x) = interp_y;
end
% Plot original and joined signals
figure;
plot(time, amplitude, 'b');
hold on; grid on;
plot(time(max_peak_locs), max_peak_values, 'ro');
plot(time, joined_signal, 'g');
% Fit exponential decay to each segment
for k = 1:length(max_peak_locs)-1
start_idx = max_peak_locs(k);
end_idx = max_peak_locs(k+1);
% Extract segment
segment_time = time(start_idx:end_idx);
segment_amplitude = joined_signal(start_idx:end_idx);
% Fit exponential decay: y = a * exp(b * x)
f = fit(segment_time, segment_amplitude, 'exp1');
decay_fits = [decay_fits; f];
% Plot fitted curve
plot(f, segment_time, segment_amplitude);
% Calculate and plot slope
start_point = [segment_time(1), f.a * exp(f.b * segment_time(1))];
end_point = [segment_time(end), f.a * exp(f.b * segment_time(end))];
plot([start_point(1), end_point(1)], [start_point(2), end_point(2)], 'c', 'LineWidth', 1.5);
% Calculate slope
slope = (end_point(2) - start_point(2)) / (end_point(1) - start_point(1));
disp(['Slope for segment ', num2str(k), ': ', num2str(slope)]);
end
hold off;
I hope it helps!
William Rose
2025 年 1 月 27 日 16:27
編集済み: William Rose
2025 年 1 月 27 日 22:29
[Fix spelling error: fit1(x,y,'exp1') should be fit(x,y,'exp1'...).]
Thank you for providing Data.xlsx.
data=importdata('Data.xlsx');
x=data.data(:,1); y=data.data(:,2:3);
figure;
subplot(211),plot(t,y(:,1),'-r');
grid on; title('Y1')
subplot(212),plot(t,y(:,2),'-b');
grid on; title('Y2'); xlabel('X')
The data y2 is the data that matches your drawing.Your code indicates that you are trying to find peaks and interpolate between the peaks. Please review findpeaks() and interp1(). They have many nice options to help you find the peaks you want, and interpolate. Then use fit(). to fit the data. You can use fittype 'exp1' to fit a monoexponential. fit(x,y,'exp1') fits the equation
Y = a*exp(b*x)
Note that the equation above decays to the asymptote x=0. If you want to include a non-zero asymptote in your fit, then you'll need to make a custom fit function, which you can do, see the help for fit().
You may want to offset the x-values: define x1=x-x0, where x0 is the time of the big peak. Then fit y versus x1. You don't have to do this. If you don't do this, you may be surprised when you get a large value for the fit estimate of a, in the equation above. That will happen because a is the fitted y-value at x=0, and if you extend the fitted exponential back to the original x=0, it will be rather large. A lot larger than the peak at x=0.04. Your choice.
For the slope (violet line in your drawing), you can compute that by the standard equation once you find the start and end points you want with findpeaks().
By the way, your data has quantization issues, as the plot below shows. It is because the data in the Excel file is rounded to one digit after the decimal point. I would invesitgate that, and try to get the data with more significant digits.
figure; plot(t,x(:,2),'-b');
xlabel('Time'); grid on; title('X2')
xlim([0.094,0.099]); ylim([2,4])
Good luck with your analysis.
1 件のコメント
William Rose
2025 年 1 月 27 日 23:49
data=importdata('Data.xlsx');
x=data.data(:,1); y=data.data(:,3);
%[pks,locs]=findpeaks(y,x); % finds too many peaks
[pks1,locs1]=findpeaks(y,x,MinPeakProminence=1);
% pks1 includes peaks on the rising shoulder which I don;t want.
[pks2,locs2]=findpeaks(y,x,MinPeakProminence=1,MinPeakDistance=0.001);
% Adding MinPeakDistance gets rid of the unwanted peaks on the rising
% shoulders.See plot in top panel, below.
% Plot data and peaks
figure, plot(x,y,'-b',locs1,pks1,'gx',locs2,pks2,'ko');
grid on; title('Y'); xlabel('X')
legend('data','pks1','pks2')
% Extract the peaks and locs with locs<0.1, and move the origin
% for locs to the first peak, as discussed in my answer above.
pks2=pks2(locs2<0.1); % do this before adjusting locs2
locs2=locs2(locs2<0.1)-locs2(1); % adjust locs2
% Fit the locs2,pks2 points with a monoexponential.
f1=fit(locs2,pks2,'exp1')
% Plot the data and fit
figure
plot(locs2(locs2<0.1),pks2(locs2<0.1),'ko')
hold on
plot(f1,'r')
grid on; xlabel('X'); ylabel('Y'); title('Fit to Peaks')
fit1 (see below) does not looks great. This must be why @Sam Chak suggested a biexponential. Let's try it.
f2=fit(locs2,pks2,'exp2')
You said originally that you also wanted slope from first to last peak (violet).
slopeEst=(pks2(end)-pks2(1))/(locs2(end)-locs2(1));
Plot fit2.
plot(f2,'g')
plot(locs2([1,end]),pks2([1,end]),Color=[.5,0,1],LineWidth=2);
legend('peaks','exp1','exp2',['slope=',num2str(slopeEst)]);
hold off
The fitted values a=23.11, b=-9698 indicate that first exponential in fit2 is esentially a delta function with amplitude 23.11. The time constant is so large and negative that you can only say it is a "really fast decay". The 95% CI for b, for fit2, is non-sensical; a more reasonable CI for b is [-Inf,-5000].
参考
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!