AeroViz 0.1.2__py3-none-any.whl → 0.1.3b0__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.

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