AeroViz 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of AeroViz might be problematic. Click here for more details.
- AeroViz/__init__.py +7 -5
- AeroViz/{config → data}/DEFAULT_DATA.csv +1 -1
- AeroViz/dataProcess/Chemistry/__init__.py +40 -40
- AeroViz/dataProcess/Chemistry/_calculate.py +15 -15
- AeroViz/dataProcess/Chemistry/_isoropia.py +72 -68
- AeroViz/dataProcess/Chemistry/_mass_volume.py +158 -161
- AeroViz/dataProcess/Chemistry/_ocec.py +109 -109
- AeroViz/dataProcess/Chemistry/_partition.py +19 -18
- AeroViz/dataProcess/Chemistry/_teom.py +9 -11
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Optical/Angstrom_exponent.py +20 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +40 -41
- AeroViz/dataProcess/Optical/__init__.py +29 -44
- AeroViz/dataProcess/Optical/_absorption.py +21 -47
- AeroViz/dataProcess/Optical/_extinction.py +31 -25
- AeroViz/dataProcess/Optical/_mie.py +5 -7
- AeroViz/dataProcess/Optical/_mie_sd.py +89 -90
- AeroViz/dataProcess/Optical/_scattering.py +19 -20
- AeroViz/dataProcess/SizeDistr/__init__.py +39 -39
- AeroViz/dataProcess/SizeDistr/__merge.py +159 -158
- AeroViz/dataProcess/SizeDistr/_merge.py +155 -154
- AeroViz/dataProcess/SizeDistr/_merge_v1.py +162 -161
- AeroViz/dataProcess/SizeDistr/_merge_v2.py +153 -152
- AeroViz/dataProcess/SizeDistr/_merge_v3.py +327 -327
- AeroViz/dataProcess/SizeDistr/_merge_v4.py +273 -275
- AeroViz/dataProcess/SizeDistr/_size_distr.py +51 -51
- AeroViz/dataProcess/VOC/__init__.py +9 -9
- AeroViz/dataProcess/VOC/_potential_par.py +53 -55
- AeroViz/dataProcess/__init__.py +28 -6
- AeroViz/dataProcess/core/__init__.py +59 -65
- AeroViz/plot/__init__.py +7 -2
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/distribution.py +421 -427
- AeroViz/plot/meteorology/meteorology.py +240 -292
- AeroViz/plot/optical/__init__.py +0 -1
- AeroViz/plot/optical/optical.py +230 -230
- AeroViz/plot/pie.py +198 -0
- AeroViz/plot/regression.py +196 -0
- AeroViz/plot/scatter.py +165 -0
- AeroViz/plot/templates/__init__.py +2 -4
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +25 -25
- AeroViz/plot/templates/corr_matrix.py +86 -93
- AeroViz/plot/templates/diurnal_pattern.py +28 -26
- AeroViz/plot/templates/koschmieder.py +59 -123
- AeroViz/plot/templates/metal_heatmap.py +135 -37
- AeroViz/plot/timeseries/__init__.py +1 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +324 -264
- AeroViz/plot/utils/__init__.py +2 -1
- AeroViz/plot/utils/_color.py +57 -57
- AeroViz/plot/utils/_unit.py +48 -48
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +5 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/process/__init__.py +17 -17
- AeroViz/process/core/DataProc.py +9 -9
- AeroViz/process/core/SizeDist.py +81 -81
- AeroViz/process/method/PyMieScatt_update.py +488 -488
- AeroViz/process/method/mie_theory.py +231 -229
- AeroViz/process/method/prop.py +40 -40
- AeroViz/process/script/AbstractDistCalc.py +103 -103
- AeroViz/process/script/Chemical.py +168 -167
- AeroViz/process/script/IMPACT.py +40 -40
- AeroViz/process/script/IMPROVE.py +152 -152
- AeroViz/process/script/Others.py +45 -45
- AeroViz/process/script/PSD.py +26 -26
- AeroViz/process/script/PSD_dry.py +69 -70
- AeroViz/process/script/retrieve_RI.py +50 -51
- AeroViz/rawDataReader/__init__.py +53 -58
- AeroViz/rawDataReader/config/supported_instruments.py +155 -0
- AeroViz/rawDataReader/core/__init__.py +233 -356
- AeroViz/rawDataReader/script/AE33.py +17 -18
- AeroViz/rawDataReader/script/AE43.py +18 -21
- AeroViz/rawDataReader/script/APS_3321.py +30 -30
- AeroViz/rawDataReader/script/Aurora.py +23 -24
- AeroViz/rawDataReader/script/BC1054.py +36 -40
- AeroViz/rawDataReader/script/EPA_vertical.py +37 -9
- AeroViz/rawDataReader/script/GRIMM.py +16 -23
- AeroViz/rawDataReader/script/IGAC.py +90 -0
- AeroViz/rawDataReader/script/MA350.py +32 -39
- AeroViz/rawDataReader/script/Minion.py +103 -0
- AeroViz/rawDataReader/script/NEPH.py +69 -74
- AeroViz/rawDataReader/script/SMPS_TH.py +25 -25
- AeroViz/rawDataReader/script/SMPS_aim11.py +32 -32
- AeroViz/rawDataReader/script/SMPS_genr.py +31 -31
- AeroViz/rawDataReader/script/Sunset_OCEC.py +60 -0
- AeroViz/rawDataReader/script/TEOM.py +30 -28
- AeroViz/rawDataReader/script/Table.py +13 -14
- AeroViz/rawDataReader/script/VOC.py +26 -0
- AeroViz/rawDataReader/script/__init__.py +18 -20
- AeroViz/tools/database.py +64 -66
- AeroViz/tools/dataclassifier.py +106 -106
- AeroViz/tools/dataprinter.py +51 -51
- AeroViz/tools/datareader.py +38 -38
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/METADATA +5 -4
- AeroViz-0.1.4.dist-info/RECORD +112 -0
- AeroViz/plot/improve/__init__.py +0 -1
- AeroViz/plot/improve/improve.py +0 -240
- AeroViz/plot/optical/aethalometer.py +0 -77
- AeroViz/plot/templates/event_evolution.py +0 -65
- AeroViz/plot/templates/regression.py +0 -256
- AeroViz/plot/templates/scatter.py +0 -130
- AeroViz/plot/templates/templates.py +0 -398
- AeroViz/plot/utils/_decorator.py +0 -74
- AeroViz/rawDataReader/script/IGAC_TH.py +0 -104
- AeroViz/rawDataReader/script/IGAC_ZM.py +0 -90
- AeroViz/rawDataReader/script/OCEC_LCRES.py +0 -34
- AeroViz/rawDataReader/script/OCEC_RES.py +0 -28
- AeroViz/rawDataReader/script/VOC_TH.py +0 -30
- AeroViz/rawDataReader/script/VOC_ZM.py +0 -37
- AeroViz/rawDataReader/utils/__init__.py +0 -0
- AeroViz/rawDataReader/utils/config.py +0 -169
- AeroViz-0.1.3.dist-info/RECORD +0 -111
- /AeroViz/{config → data}/DEFAULT_PNSD_DATA.csv +0 -0
- /AeroViz/{config → rawDataReader/config}/__init__.py +0 -0
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -16,567 +16,561 @@ from tabulate import tabulate
|
|
|
16
16
|
from AeroViz.plot.utils import *
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
'plot_dist',
|
|
20
|
+
'heatmap',
|
|
21
|
+
'heatmap_tms',
|
|
22
|
+
'three_dimension',
|
|
23
|
+
'curve_fitting'
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@set_figure
|
|
28
28
|
def plot_dist(data: DataFrame | np.ndarray,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
ax.legend(legends_combined, labels_combined, prop={'weight': 'bold'})
|
|
120
|
-
|
|
121
|
-
plt.show()
|
|
122
|
-
|
|
123
|
-
return fig, ax
|
|
29
|
+
data_std: DataFrame | None = None,
|
|
30
|
+
std_scale: float | None = 1,
|
|
31
|
+
unit: Literal["Number", "Surface", "Volume", "Extinction"] = 'Number',
|
|
32
|
+
additional: Literal["Std", "Enhancement", "Error"] = None,
|
|
33
|
+
fig: Figure | None = None,
|
|
34
|
+
ax: Axes | None = None,
|
|
35
|
+
**kwargs
|
|
36
|
+
) -> tuple[Figure, Axes]:
|
|
37
|
+
"""
|
|
38
|
+
Plot particle size distribution curves and optionally show enhancements.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
data : dict or list
|
|
43
|
+
If dict, keys are labels and values are arrays of distribution values.
|
|
44
|
+
If listed, it should contain three arrays for different curves.
|
|
45
|
+
data_std : dict
|
|
46
|
+
Dictionary containing standard deviation data for ambient extinction distribution.
|
|
47
|
+
std_scale : float
|
|
48
|
+
The width of standard deviation.
|
|
49
|
+
unit : {'Number', 'Surface', 'Volume', 'Extinction'}
|
|
50
|
+
Unit of measurement for the data.
|
|
51
|
+
additional : {'std', 'enhancement', 'error'}
|
|
52
|
+
Whether to show enhancement curves.
|
|
53
|
+
fig : Figure, optional
|
|
54
|
+
Matplotlib Figure object to use.
|
|
55
|
+
ax : AxesSubplot, optional
|
|
56
|
+
Matplotlib AxesSubplot object to use. If not provided, a new subplot will be created.
|
|
57
|
+
**kwargs : dict
|
|
58
|
+
Additional keyword arguments.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
ax : AxesSubplot
|
|
63
|
+
Matplotlib AxesSubplot.
|
|
64
|
+
|
|
65
|
+
Examples
|
|
66
|
+
--------
|
|
67
|
+
>>> plot_dist(DataFrame(...), additional="Enhancement")
|
|
68
|
+
"""
|
|
69
|
+
fig, ax = plt.subplots(**{**{'figsize': (6, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
|
|
70
|
+
ax.get_figure(), ax)
|
|
71
|
+
|
|
72
|
+
# plot_kws
|
|
73
|
+
plot_kws = dict(ls='solid', lw=2, alpha=0.8, **kwargs.get('plot_kws', {}))
|
|
74
|
+
|
|
75
|
+
# Receive input data
|
|
76
|
+
dp = np.array(data.columns, dtype=float)
|
|
77
|
+
states = np.array(data.index)
|
|
78
|
+
|
|
79
|
+
for state in states:
|
|
80
|
+
mean = data.loc[state].to_numpy()
|
|
81
|
+
ax.plot(dp, mean, label=state, color=Color.color_choose[state][0], **plot_kws)
|
|
82
|
+
|
|
83
|
+
if additional == 'Std':
|
|
84
|
+
std = data_std.loc[state].to_numpy() * std_scale
|
|
85
|
+
ax.fill_between(dp, y1=mean - std, y2=mean + std, alpha=0.4, color=Color.color_choose[state][1],
|
|
86
|
+
edgecolor=None, label='__nolegend__')
|
|
87
|
+
|
|
88
|
+
# figure_set
|
|
89
|
+
ax.set(xlim=(dp.min(), dp.max()), ylim=(0, None), xscale='log',
|
|
90
|
+
xlabel=r'$D_{p} (nm)$', ylabel=Unit(f'{unit}_dist'), title=kwargs.get('title', unit))
|
|
91
|
+
|
|
92
|
+
ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 3), useMathText=True)
|
|
93
|
+
ax.grid(axis='x', which='major', color='k', linestyle='dashdot', linewidth=0.4, alpha=0.4)
|
|
94
|
+
|
|
95
|
+
Clean = data.loc['Clean'].to_numpy()
|
|
96
|
+
Transition = data.loc['Transition'].to_numpy()
|
|
97
|
+
Event = data.loc['Event'].to_numpy()
|
|
98
|
+
|
|
99
|
+
if additional == "Enhancement":
|
|
100
|
+
ax2 = ax.twinx()
|
|
101
|
+
ax2.plot(dp, Transition / Clean, ls='dashed', color='k', label=f'{additional} ratio 1')
|
|
102
|
+
ax2.plot(dp, Event / Transition, ls='dashed', color='gray', label=f'{additional} ratio 2')
|
|
103
|
+
ax2.set(ylabel='Enhancement ratio')
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
ax2 = ax.twinx()
|
|
107
|
+
error1 = np.where(Transition != 0, np.abs(Clean - Transition) / Clean * 100, 0)
|
|
108
|
+
error2 = np.where(Event != 0, np.abs(Transition - Event) / Transition * 100, 0)
|
|
109
|
+
|
|
110
|
+
ax2.plot(dp, error1, ls='--', color='k', label='Error 1 ')
|
|
111
|
+
ax2.plot(dp, error2, ls='--', color='gray', label='Error 2')
|
|
112
|
+
ax2.set(ylabel='Error (%)')
|
|
113
|
+
|
|
114
|
+
ax.legend(*combine_legends(fig.get_axes()), prop={'weight': 'bold'})
|
|
115
|
+
|
|
116
|
+
plt.show()
|
|
117
|
+
|
|
118
|
+
return fig, ax
|
|
124
119
|
|
|
125
120
|
|
|
126
121
|
@set_figure
|
|
127
122
|
def heatmap(data: DataFrame,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
unit: Literal["Number", "Surface", "Volume", "Extinction"],
|
|
124
|
+
cmap: str = 'Blues',
|
|
125
|
+
colorbar: bool = False,
|
|
126
|
+
magic_number: int = 11,
|
|
127
|
+
ax: Axes | None = None,
|
|
128
|
+
**kwargs
|
|
129
|
+
) -> tuple[Figure, Axes]:
|
|
130
|
+
"""
|
|
131
|
+
Plot a heatmap of particle size distribution.
|
|
137
132
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
data : pandas.DataFrame
|
|
136
|
+
The data containing particle size distribution values. Each column corresponds to a size bin,
|
|
137
|
+
and each row corresponds to a different distribution.
|
|
143
138
|
|
|
144
|
-
|
|
145
|
-
|
|
139
|
+
unit : {'Number', 'Surface', 'Volume', 'Extinction'}, optional
|
|
140
|
+
The unit of measurement for the data.
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
142
|
+
cmap : str, default='Blues'
|
|
143
|
+
The colormap to use for the heatmap.
|
|
149
144
|
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
colorbar : bool, default=False
|
|
146
|
+
Whether to show the colorbar.
|
|
152
147
|
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
magic_number : int, default=11
|
|
149
|
+
The number of bins to use for the histogram.
|
|
155
150
|
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
ax : matplotlib.axes.Axes, optional
|
|
152
|
+
The axes to plot the heatmap on. If not provided, a new subplot will be created.
|
|
158
153
|
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
**kwargs
|
|
155
|
+
Additional keyword arguments to pass to matplotlib functions.
|
|
161
156
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
matplotlib.axes.Axes
|
|
160
|
+
The Axes object containing the heatmap.
|
|
166
161
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
Examples
|
|
163
|
+
--------
|
|
164
|
+
>>> heatmap(DataFrame(...), unit='Number')
|
|
170
165
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
Notes
|
|
167
|
+
-----
|
|
168
|
+
This function calculates a 2D histogram of the log-transformed particle sizes and the distribution values.
|
|
169
|
+
It then plots the heatmap using a logarithmic color scale.
|
|
175
170
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
"""
|
|
172
|
+
fig, ax = plt.subplots(**{**{'figsize': (3, 3)}, **kwargs.get('fig_kws', {})}) if ax is None else (
|
|
173
|
+
ax.get_figure(), ax)
|
|
179
174
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
min_value = 1e-8
|
|
176
|
+
dp = np.array(data.columns, dtype=float)
|
|
177
|
+
x = np.append(np.tile(dp, data.to_numpy().shape[0]), np.log(dp).max())
|
|
178
|
+
y = np.append(data.to_numpy().flatten(), min_value)
|
|
184
179
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
180
|
+
# mask NaN
|
|
181
|
+
x = x[~np.isnan(y)]
|
|
182
|
+
y = y[~np.isnan(y)]
|
|
188
183
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
# using log(x)
|
|
185
|
+
histogram, xedges, yedges = np.histogram2d(np.log(x), y, bins=len(dp) + magic_number)
|
|
186
|
+
histogram[histogram == 0] = min_value # Avoid log(0)
|
|
192
187
|
|
|
193
|
-
|
|
188
|
+
plot_kws = dict(norm=colors.LogNorm(vmin=1, vmax=histogram.max()), cmap=cmap, **kwargs.get('plot_kws', {}))
|
|
194
189
|
|
|
195
|
-
|
|
190
|
+
pco = ax.pcolormesh(xedges[:-1], yedges[:-1], histogram.T, shading='gouraud', **plot_kws)
|
|
196
191
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
ax.plot(np.log(dp), data.mean() - data.std(), ls='dashed', color='b', label='clean')
|
|
192
|
+
ax.plot(np.log(dp), data.mean() + data.std(), ls='dashed', color='r', label='pollutant')
|
|
193
|
+
ax.plot(np.log(dp), data.mean(), ls='dashed', color='k', alpha=0.5, label='mean')
|
|
194
|
+
ax.plot(np.log(dp), data.mean() - data.std(), ls='dashed', color='b', label='clean')
|
|
201
195
|
|
|
202
|
-
|
|
203
|
-
|
|
196
|
+
ax.set(xlim=(np.log(dp).min(), np.log(dp).max()), ylim=(0, None),
|
|
197
|
+
xlabel=r'$D_{p} (nm)$', ylabel=Unit(f'{unit}_dist'), title=kwargs.get('title', unit))
|
|
204
198
|
|
|
205
|
-
|
|
206
|
-
|
|
199
|
+
major_ticks = np.power(10, np.arange(np.ceil(np.log10(dp.min())), np.floor(np.log10(dp.max())) + 1))
|
|
200
|
+
minor_ticks = [v for v in np.concatenate([_ * np.arange(2, 10) for _ in major_ticks]) if min(dp) <= v <= max(dp)]
|
|
207
201
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
ax.set_xticks(np.log(major_ticks))
|
|
203
|
+
ax.set_xticks(np.log(minor_ticks), minor=True)
|
|
204
|
+
ax.xaxis.set_major_formatter(FuncFormatter(lambda tick, pos: "{:.0f}".format(np.exp(tick))))
|
|
211
205
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
206
|
+
ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 3), useMathText=True)
|
|
207
|
+
ax.grid(axis='x', which='major', color='k', linestyle='dashdot', linewidth=0.4, alpha=0.4)
|
|
208
|
+
ax.legend(prop={'weight': 'bold'})
|
|
215
209
|
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
if colorbar:
|
|
211
|
+
plt.colorbar(pco, pad=0.02, fraction=0.05, label='Counts', **kwargs.get('cbar_kws', {}))
|
|
218
212
|
|
|
219
|
-
|
|
213
|
+
plt.show()
|
|
220
214
|
|
|
221
|
-
|
|
215
|
+
return fig, ax
|
|
222
216
|
|
|
223
217
|
|
|
224
218
|
@set_figure
|
|
225
219
|
def heatmap_tms(data: DataFrame,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
220
|
+
unit: Literal["Number", "Surface", "Volume", "Extinction"],
|
|
221
|
+
cmap: str = 'jet',
|
|
222
|
+
ax: Axes | None = None,
|
|
223
|
+
**kwargs
|
|
224
|
+
) -> tuple[Figure, Axes]:
|
|
225
|
+
""" Plot the size distribution over time.
|
|
232
226
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
data : DataFrame
|
|
230
|
+
A DataFrame of particle concentrations to plot the heatmap.
|
|
237
231
|
|
|
238
|
-
|
|
239
|
-
|
|
232
|
+
ax : matplotlib.axis.Axis
|
|
233
|
+
An axis object to plot on. If none is provided, one will be created.
|
|
240
234
|
|
|
241
|
-
|
|
242
|
-
|
|
235
|
+
unit : Literal["Number", "Surface", "Volume", "Extinction"]
|
|
236
|
+
default='Number'
|
|
243
237
|
|
|
244
|
-
|
|
245
|
-
|
|
238
|
+
cmap : matplotlib.colormap, default='viridis'
|
|
239
|
+
The colormap to use. Can be anything other that 'jet'.
|
|
246
240
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
ax : matplotlib.axis.Axis
|
|
250
244
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
245
|
+
Notes
|
|
246
|
+
-----
|
|
247
|
+
Do not dropna when using this code.
|
|
254
248
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
249
|
+
Examples
|
|
250
|
+
--------
|
|
251
|
+
Plot a SPMS + APS data:
|
|
252
|
+
>>> heatmap_tms(DataFrame(...), cmap='jet')
|
|
253
|
+
"""
|
|
254
|
+
fig, ax = plt.subplots(
|
|
255
|
+
**{**{'figsize': (len(data.index) * 0.01, 2)}, **kwargs.get('fig_kws', {})}) if ax is None else (
|
|
256
|
+
ax.get_figure(), ax)
|
|
263
257
|
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
time = data.index
|
|
259
|
+
dp = np.array(data.columns, dtype=float)
|
|
266
260
|
|
|
267
|
-
|
|
268
|
-
|
|
261
|
+
# data = data.interpolate(method='linear', axis=0)
|
|
262
|
+
data = np.nan_to_num(data.to_numpy())
|
|
269
263
|
|
|
270
|
-
|
|
264
|
+
vmin_mapping = {'Number': 1e2, 'Surface': 1e8, 'Volume': 1e9, 'Extinction': 1}
|
|
271
265
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
266
|
+
# Set the colorbar min and max based on the min and max of the values
|
|
267
|
+
cbar_min = kwargs.get('cbar_kws', {}).pop('cbar_min', vmin_mapping[unit])
|
|
268
|
+
cbar_max = kwargs.get('cbar_kws', {}).pop('cbar_max', np.nanmax(data))
|
|
275
269
|
|
|
276
|
-
|
|
277
|
-
|
|
270
|
+
# Set the plot_kws
|
|
271
|
+
plot_kws = dict(norm=colors.LogNorm(vmin=cbar_min, vmax=cbar_max), cmap=cmap, **kwargs.get('plot_kws', {}))
|
|
278
272
|
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
# main plot
|
|
274
|
+
pco = ax.pcolormesh(time, dp, data.T, shading='auto', **plot_kws)
|
|
281
275
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
276
|
+
# Set ax
|
|
277
|
+
st_tm, fn_tm = time[0], time[-1]
|
|
278
|
+
tick_time = date_range(st_tm, fn_tm, freq=kwargs.get('freq', '10d')).strftime("%F")
|
|
285
279
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
280
|
+
ax.set(xlim=(st_tm, fn_tm),
|
|
281
|
+
ylim=(dp.min(), dp.max()),
|
|
282
|
+
ylabel='$D_p (nm)$',
|
|
283
|
+
xticks=tick_time,
|
|
284
|
+
xticklabels=tick_time,
|
|
285
|
+
yscale='log',
|
|
286
|
+
title=kwargs.get('title', f'{st_tm.strftime("%F")} - {fn_tm.strftime("%F")}'))
|
|
293
287
|
|
|
294
|
-
|
|
288
|
+
plt.colorbar(pco, pad=0.02, fraction=0.02, label=Unit(f'{unit}_dist'), **kwargs.get('cbar_kws', {}))
|
|
295
289
|
|
|
296
|
-
|
|
290
|
+
plt.show()
|
|
297
291
|
|
|
298
|
-
|
|
292
|
+
return fig, ax
|
|
299
293
|
|
|
300
294
|
|
|
301
295
|
@set_figure
|
|
302
296
|
def three_dimension(data: DataFrame | np.ndarray,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
297
|
+
unit: Literal["Number", "Surface", "Volume", "Extinction"],
|
|
298
|
+
cmap: str = 'Blues',
|
|
299
|
+
ax: Axes | None = None,
|
|
300
|
+
**kwargs
|
|
301
|
+
) -> tuple[Figure, Axes]:
|
|
302
|
+
"""
|
|
303
|
+
Create a 3D plot with data from a pandas DataFrame or numpy array.
|
|
310
304
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
data : DataFrame or ndarray
|
|
308
|
+
Input data containing the values to be plotted.
|
|
315
309
|
|
|
316
|
-
|
|
317
|
-
|
|
310
|
+
unit : {'Number', 'Surface', 'Volume', 'Extinction'}
|
|
311
|
+
Unit of measurement for the data.
|
|
318
312
|
|
|
319
|
-
|
|
320
|
-
|
|
313
|
+
cmap : str, default='Blues'
|
|
314
|
+
The colormap to use for the facecolors.
|
|
321
315
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
316
|
+
ax : AxesSubplot, optional
|
|
317
|
+
Matplotlib AxesSubplot. If not provided, a new subplot will be created.
|
|
318
|
+
**kwargs
|
|
319
|
+
Additional keyword arguments to customize the plot.
|
|
326
320
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
Axes
|
|
324
|
+
Matplotlib Axes object representing the 3D plot.
|
|
331
325
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
326
|
+
Notes
|
|
327
|
+
-----
|
|
328
|
+
- The function creates a 3D plot with data provided in a pandas DataFrame or numpy array.
|
|
329
|
+
- The x-axis is logarithmically scaled, and ticks and labels are formatted accordingly.
|
|
330
|
+
- Additional customization can be done using the **kwargs.
|
|
337
331
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
332
|
+
Example
|
|
333
|
+
-------
|
|
334
|
+
>>> three_dimension(DataFrame(...), unit='Number', cmap='Blues')
|
|
335
|
+
"""
|
|
336
|
+
fig, ax = plt.subplots(figsize=(4, 4), subplot_kw={"projection": "3d"},
|
|
337
|
+
**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
344
338
|
|
|
345
|
-
|
|
346
|
-
|
|
339
|
+
dp = np.array(['11.7', *data.columns, '2437.4'], dtype=float)
|
|
340
|
+
lines = data.shape[0]
|
|
347
341
|
|
|
348
|
-
|
|
349
|
-
|
|
342
|
+
_X, _Y = np.meshgrid(np.log(dp), np.arange(lines))
|
|
343
|
+
_Z = np.pad(data, ((0, 0), (1, 1)), 'constant')
|
|
350
344
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
345
|
+
verts = []
|
|
346
|
+
for i in range(_X.shape[0]):
|
|
347
|
+
verts.append(list(zip(_X[i, :], _Z[i, :])))
|
|
354
348
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
349
|
+
facecolors = plt.colormaps[cmap](np.linspace(0, 1, len(verts)))
|
|
350
|
+
poly = PolyCollection(verts, facecolors=facecolors, edgecolors='k', lw=0.5, alpha=.7)
|
|
351
|
+
ax.add_collection3d(poly, zs=range(1, lines + 1), zdir='y')
|
|
358
352
|
|
|
359
|
-
|
|
360
|
-
|
|
353
|
+
ax.set(xlim=(np.log(11.7), np.log(2437.4)), ylim=(1, lines), zlim=(0, np.nanmax(_Z)),
|
|
354
|
+
xlabel='$D_{p} (nm)$', ylabel='Class', zlabel=Unit(f'{unit}_dist'))
|
|
361
355
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
356
|
+
ax.set_xticks(np.log([10, 100, 1000]))
|
|
357
|
+
ax.set_xticks(np.log([20, 30, 40, 50, 60, 70, 80, 90, 200, 300, 400, 500, 600, 700, 800, 900, 2000]), minor=True)
|
|
358
|
+
ax.xaxis.set_major_formatter(FuncFormatter((lambda tick, pos: "{:.0f}".format(np.exp(tick)))))
|
|
359
|
+
ax.ticklabel_format(axis='z', style='sci', scilimits=(0, 3), useMathText=True)
|
|
366
360
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
361
|
+
ax.zaxis.get_offset_text().set_visible(False)
|
|
362
|
+
exponent = np.floor(np.log10(np.nanmax(data))).astype(int)
|
|
363
|
+
ax.text(ax.get_xlim()[1] * 1.05, ax.get_ylim()[1], ax.get_zlim()[1] * 1.1, s=fr'${{\times}}\ 10^{exponent}$')
|
|
370
364
|
|
|
371
|
-
|
|
365
|
+
plt.show()
|
|
372
366
|
|
|
373
|
-
|
|
367
|
+
return fig, ax
|
|
374
368
|
|
|
375
369
|
|
|
376
370
|
@set_figure
|
|
377
371
|
def curve_fitting(dp: np.ndarray,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
372
|
+
dist: np.ndarray | Series | DataFrame,
|
|
373
|
+
mode: int = None,
|
|
374
|
+
unit: Literal["Number", "Surface", "Volume", "Extinction"] = None,
|
|
375
|
+
ax: Axes | None = None,
|
|
376
|
+
**kwargs
|
|
377
|
+
) -> tuple[Figure, Axes]:
|
|
378
|
+
"""
|
|
379
|
+
Fit a log-normal distribution to the given data and plot the result.
|
|
386
380
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
- dp (array): Array of diameter values.
|
|
384
|
+
- dist (array): Array of distribution values corresponding to each diameter.
|
|
385
|
+
- mode (int, optional): Number of log-normal distribution to fit (default is None).
|
|
386
|
+
- **kwargs: Additional keyword arguments to be passed to the plot_function.
|
|
393
387
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
388
|
+
Returns
|
|
389
|
+
-------
|
|
390
|
+
None
|
|
397
391
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
392
|
+
Notes
|
|
393
|
+
-----
|
|
394
|
+
- The function fits a sum of log-normal distribution to the input data.
|
|
395
|
+
- The number of distribution is determined by the 'mode' parameter.
|
|
396
|
+
- Additional plotting customization can be done using the **kwargs.
|
|
403
397
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
398
|
+
Example
|
|
399
|
+
-------
|
|
400
|
+
>>> curve_fitting(dp, dist, mode=2, xlabel="Diameter (nm)", ylabel="Distribution")
|
|
401
|
+
"""
|
|
402
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
|
|
409
403
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
404
|
+
# Calculate total number concentration and normalize distribution
|
|
405
|
+
total_num = np.sum(dist * log(dp))
|
|
406
|
+
norm_data = dist / total_num
|
|
413
407
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
408
|
+
def lognorm_func(x, *params):
|
|
409
|
+
num_distributions = len(params) // 3
|
|
410
|
+
result = np.zeros_like(x)
|
|
417
411
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
412
|
+
for i in range(num_distributions):
|
|
413
|
+
offset = i * 3
|
|
414
|
+
_number, _geomean, _geostd = params[offset: offset + 3]
|
|
421
415
|
|
|
422
|
-
|
|
423
|
-
|
|
416
|
+
result += (_number / (log(_geostd) * sqrt(2 * pi)) *
|
|
417
|
+
exp(-(log(x) - log(_geomean)) ** 2 / (2 * log(_geostd) ** 2)))
|
|
424
418
|
|
|
425
|
-
|
|
419
|
+
return result
|
|
426
420
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
421
|
+
# initial gauss
|
|
422
|
+
min_value = np.array([min(dist)])
|
|
423
|
+
extend_ser = np.concatenate([min_value, dist, min_value])
|
|
424
|
+
_mode, _ = find_peaks(extend_ser, distance=20)
|
|
425
|
+
peak = dp[_mode - 1]
|
|
426
|
+
mode = mode or len(peak)
|
|
433
427
|
|
|
434
|
-
|
|
435
|
-
|
|
428
|
+
# 初始參數猜測
|
|
429
|
+
initial_guess = [0.05, 20., 2.] * mode
|
|
436
430
|
|
|
437
|
-
|
|
438
|
-
|
|
431
|
+
# 設定參數範圍
|
|
432
|
+
bounds = ([1e-6, 10, 1] * mode, [1, 3000, 8] * mode)
|
|
439
433
|
|
|
440
|
-
|
|
441
|
-
|
|
434
|
+
# 使用 curve_fit 函數進行擬合
|
|
435
|
+
result = curve_fit(lognorm_func, dp, norm_data, p0=initial_guess, bounds=bounds)
|
|
442
436
|
|
|
443
|
-
|
|
444
|
-
|
|
437
|
+
# 獲取擬合的參數
|
|
438
|
+
params = result[0].tolist()
|
|
445
439
|
|
|
446
|
-
|
|
447
|
-
|
|
440
|
+
print('\n' + "Fitting Results:")
|
|
441
|
+
table = []
|
|
448
442
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
443
|
+
for i in range(mode):
|
|
444
|
+
offset = i * 3
|
|
445
|
+
num, mu, sigma = params[offset:offset + 3]
|
|
446
|
+
table.append([f'log-{i + 1}', f"{num * total_num:.3f}", f"{mu:.3f}", f"{sigma:.3f}"])
|
|
453
447
|
|
|
454
|
-
|
|
455
|
-
|
|
448
|
+
# 使用 tabulate 來建立表格並印出
|
|
449
|
+
print(tabulate(table, headers=["log-", "number", "mu", "sigma"], floatfmt=".3f", tablefmt="fancy_grid"))
|
|
456
450
|
|
|
457
|
-
|
|
451
|
+
fit_curve = total_num * lognorm_func(dp, *params)
|
|
458
452
|
|
|
459
|
-
|
|
460
|
-
|
|
453
|
+
plt.plot(dp, fit_curve, color='#c41b1b', label='Fitting curve', lw=2.5)
|
|
454
|
+
plt.plot(dp, dist, color='b', label='Observed curve', lw=2.5)
|
|
461
455
|
|
|
462
|
-
|
|
463
|
-
|
|
456
|
+
ax.set(xlim=(dp.min(), dp.max()), ylim=(0, None), xscale='log',
|
|
457
|
+
xlabel=r'$\bf D_{p}\ (nm)$', ylabel=Unit(f'{unit}_dist'), title=kwargs.get('title'))
|
|
464
458
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
459
|
+
plt.grid(color='k', axis='x', which='major', linestyle='dashdot', linewidth=0.4, alpha=0.4)
|
|
460
|
+
ax.ticklabel_format(axis='y', style='sci', scilimits=(0, 3), useMathText=True)
|
|
461
|
+
ax.legend(prop={'weight': 'bold'})
|
|
468
462
|
|
|
469
|
-
|
|
463
|
+
plt.show(block=True)
|
|
470
464
|
|
|
471
|
-
|
|
465
|
+
return fig, ax
|
|
472
466
|
|
|
473
467
|
|
|
474
468
|
@set_figure
|
|
475
469
|
def ls_mode(**kwargs) -> tuple[Figure, Axes]:
|
|
476
|
-
|
|
477
|
-
|
|
470
|
+
"""
|
|
471
|
+
Plot log-normal mass size distribution for small mode, large mode, and sea salt particles.
|
|
478
472
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
**kwargs : dict
|
|
476
|
+
Additional keyword arguments.
|
|
483
477
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
478
|
+
Examples
|
|
479
|
+
--------
|
|
480
|
+
Example : Plot log-normal mass size distribution with default settings
|
|
481
|
+
>>> ls_mode()
|
|
482
|
+
"""
|
|
489
483
|
|
|
490
|
-
|
|
484
|
+
fig, ax = plt.subplots(**kwargs.get('fig_kws', {}))
|
|
491
485
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
486
|
+
geoMean = [0.2, 0.5, 2.5]
|
|
487
|
+
geoStdv = [2.2, 1.5, 2.0]
|
|
488
|
+
color = ['g', 'r', 'b']
|
|
489
|
+
label = [r'$\bf Small\ mode\ :D_{g}\ =\ 0.2\ \mu m,\ \sigma_{{g}}\ =\ 2.2$',
|
|
490
|
+
r'$\bf Large\ mode\ :D_{g}\ =\ 0.5\ \mu m,\ \sigma_{{g}}\ =\ 1.5$',
|
|
491
|
+
r'$\bf Sea\ salt\ :D_{g}\ =\ 2.5\ \mu m,\ \sigma_{{g}}\ =\ 2.0$']
|
|
498
492
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
493
|
+
x = np.geomspace(0.001, 20, 10000)
|
|
494
|
+
for _gmd, _gsd, _color, _label in zip(geoMean, geoStdv, color, label):
|
|
495
|
+
lognorm = 1 / (log(_gsd) * sqrt(2 * pi)) * (exp(-(log(x) - log(_gmd)) ** 2 / (2 * log(_gsd) ** 2)))
|
|
502
496
|
|
|
503
|
-
|
|
504
|
-
|
|
497
|
+
ax.semilogx(x, lognorm, color=_color, label=_label)
|
|
498
|
+
ax.fill_between(x, lognorm, 0, where=(lognorm > 0), color=_color, alpha=0.3, label='__nolegend__')
|
|
505
499
|
|
|
506
|
-
|
|
507
|
-
|
|
500
|
+
ax.set(xlim=(0.001, 20), ylim=(0, None), xscale='log', xlabel=r'$\bf D_{p}\ (nm)$',
|
|
501
|
+
ylabel=r'$\bf Probability\ (dM/dlogdp)$', title=r'Log-normal Mass Size Distribution')
|
|
508
502
|
|
|
509
|
-
|
|
510
|
-
|
|
503
|
+
ax.grid(color='k', axis='x', which='major', linestyle='dashdot', linewidth=0.4, alpha=0.4)
|
|
504
|
+
ax.legend(prop={'weight': 'bold'})
|
|
511
505
|
|
|
512
|
-
|
|
506
|
+
plt.show()
|
|
513
507
|
|
|
514
|
-
|
|
508
|
+
return fig, ax
|
|
515
509
|
|
|
516
510
|
|
|
517
511
|
@set_figure
|
|
518
512
|
def lognorm_dist(**kwargs) -> tuple[Figure, Axes]:
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
513
|
+
#
|
|
514
|
+
"""
|
|
515
|
+
Plot various particle size distribution to illustrate log-normal distribution and transformations.
|
|
522
516
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
517
|
+
Parameters
|
|
518
|
+
----------
|
|
519
|
+
**kwargs : dict
|
|
520
|
+
Additional keyword arguments.
|
|
527
521
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
522
|
+
Examples
|
|
523
|
+
--------
|
|
524
|
+
Example : Plot default particle size distribution
|
|
525
|
+
>>> lognorm_dist()
|
|
526
|
+
"""
|
|
533
527
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
528
|
+
fig, ax = plt.subplots(2, 2, **kwargs.get('fig_kws', {}))
|
|
529
|
+
([ax1, ax2], [ax3, ax4]) = ax
|
|
530
|
+
fig.suptitle('Particle Size Distribution', fontweight='bold')
|
|
531
|
+
plt.subplots_adjust(left=0.125, right=0.925, bottom=0.1, top=0.93, wspace=0.4, hspace=0.4)
|
|
538
532
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
533
|
+
# pdf
|
|
534
|
+
normpdf = lambda x, mu, sigma: (1 / (sigma * sqrt(2 * pi))) * exp(-(x - mu) ** 2 / (2 * sigma ** 2))
|
|
535
|
+
lognormpdf = lambda x, gmean, gstd: (1 / (log(gstd) * sqrt(2 * pi))) * exp(
|
|
536
|
+
-(log(x) - log(gmean)) ** 2 / (2 * log(gstd) ** 2))
|
|
537
|
+
lognormpdf2 = lambda x, gmean, gstd: (1 / (x * log(gstd) * sqrt(2 * pi))) * exp(
|
|
538
|
+
-(log(x) - log(gmean)) ** 2 / (2 * log(gstd) ** 2))
|
|
545
539
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
540
|
+
# 生成x
|
|
541
|
+
x = np.linspace(-10, 10, 1000)
|
|
542
|
+
x2 = np.geomspace(0.01, 100, 1000)
|
|
549
543
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
544
|
+
# Question 1
|
|
545
|
+
# 若對數常態分布x有gmd=3, gstd=2,ln(x) ~ 常態分佈,試問其分布的平均值與標準差?? Y ~ N(mu=log(gmean), sigma=log(gstd))
|
|
546
|
+
data1 = lognorm(scale=3, s=log(2)).rvs(size=5000)
|
|
553
547
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
548
|
+
# Question 2
|
|
549
|
+
# 若常態分布x有平均值3 標準差1,exp(x)則為一對數常態分佈? 由對數常態分佈的定義 若隨機變數ln(Z)是常態分布 則Z為對數常態分布
|
|
550
|
+
# 因此已知Z = exp(x), so ln(Z)=x,Z ~ 對數常態分佈,試問其分布的幾何平均值與幾何標準差是?? Z ~ LN(geoMean=exp(mu), geoStd=exp(sigma))
|
|
551
|
+
data2 = norm(loc=3, scale=1).rvs(size=5000)
|
|
558
552
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
553
|
+
def plot_distribution(ax, x, pdf, color='k-', xscale='linear'):
|
|
554
|
+
ax.plot(x, pdf, color)
|
|
555
|
+
ax.set(xlabel='Particle Size (micron)', ylabel='Probability Density', xlim=(x.min(), x.max()), xscale=xscale)
|
|
562
556
|
|
|
563
|
-
|
|
564
|
-
|
|
557
|
+
# 繪製粒徑分布
|
|
558
|
+
plot_distribution(ax1, x, normpdf(x, mu=0, sigma=2))
|
|
565
559
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
560
|
+
plot_distribution(ax2, x2, lognormpdf(x2, gmean=0.8, gstd=1.5), 'g-', xscale='log')
|
|
561
|
+
plot_distribution(ax2, x2, lognormpdf2(x2, gmean=0.8, gstd=1.5), 'r--', xscale='log')
|
|
562
|
+
plot_distribution(ax2, x2, lognorm(scale=0.8, s=log(1.5)).pdf(x2), 'b--', xscale='log')
|
|
569
563
|
|
|
570
|
-
|
|
571
|
-
|
|
564
|
+
plot_distribution(ax3, x, normpdf(x, mu=log(3), sigma=log(2)), 'k-')
|
|
565
|
+
ax3.hist(log(data1), bins=100, density=True, alpha=0.6, color='g')
|
|
572
566
|
|
|
573
|
-
|
|
574
|
-
|
|
567
|
+
plot_distribution(ax4, x2, lognormpdf2(x2, gmean=exp(3), gstd=exp(1)), 'r-', xscale='log')
|
|
568
|
+
ax4.hist(exp(data2), bins=100, density=True, alpha=0.6, color='g')
|
|
575
569
|
|
|
576
|
-
|
|
570
|
+
plt.show()
|
|
577
571
|
|
|
578
|
-
|
|
572
|
+
return fig, ax
|
|
579
573
|
|
|
580
574
|
|
|
581
575
|
if __name__ == '__main__':
|
|
582
|
-
|
|
576
|
+
lognorm_dist()
|