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.

Files changed (121) hide show
  1. AeroViz/__init__.py +7 -5
  2. AeroViz/{config → data}/DEFAULT_DATA.csv +1 -1
  3. AeroViz/dataProcess/Chemistry/__init__.py +40 -40
  4. AeroViz/dataProcess/Chemistry/_calculate.py +15 -15
  5. AeroViz/dataProcess/Chemistry/_isoropia.py +72 -68
  6. AeroViz/dataProcess/Chemistry/_mass_volume.py +158 -161
  7. AeroViz/dataProcess/Chemistry/_ocec.py +109 -109
  8. AeroViz/dataProcess/Chemistry/_partition.py +19 -18
  9. AeroViz/dataProcess/Chemistry/_teom.py +9 -11
  10. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  11. AeroViz/dataProcess/Optical/Angstrom_exponent.py +20 -0
  12. AeroViz/dataProcess/Optical/_IMPROVE.py +40 -41
  13. AeroViz/dataProcess/Optical/__init__.py +29 -44
  14. AeroViz/dataProcess/Optical/_absorption.py +21 -47
  15. AeroViz/dataProcess/Optical/_extinction.py +31 -25
  16. AeroViz/dataProcess/Optical/_mie.py +5 -7
  17. AeroViz/dataProcess/Optical/_mie_sd.py +89 -90
  18. AeroViz/dataProcess/Optical/_scattering.py +19 -20
  19. AeroViz/dataProcess/SizeDistr/__init__.py +39 -39
  20. AeroViz/dataProcess/SizeDistr/__merge.py +159 -158
  21. AeroViz/dataProcess/SizeDistr/_merge.py +155 -154
  22. AeroViz/dataProcess/SizeDistr/_merge_v1.py +162 -161
  23. AeroViz/dataProcess/SizeDistr/_merge_v2.py +153 -152
  24. AeroViz/dataProcess/SizeDistr/_merge_v3.py +327 -327
  25. AeroViz/dataProcess/SizeDistr/_merge_v4.py +273 -275
  26. AeroViz/dataProcess/SizeDistr/_size_distr.py +51 -51
  27. AeroViz/dataProcess/VOC/__init__.py +9 -9
  28. AeroViz/dataProcess/VOC/_potential_par.py +53 -55
  29. AeroViz/dataProcess/__init__.py +28 -6
  30. AeroViz/dataProcess/core/__init__.py +59 -65
  31. AeroViz/plot/__init__.py +7 -2
  32. AeroViz/plot/bar.py +126 -0
  33. AeroViz/plot/box.py +69 -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 +196 -0
  40. AeroViz/plot/scatter.py +165 -0
  41. AeroViz/plot/templates/__init__.py +2 -4
  42. AeroViz/plot/templates/ammonium_rich.py +34 -0
  43. AeroViz/plot/templates/contour.py +25 -25
  44. AeroViz/plot/templates/corr_matrix.py +86 -93
  45. AeroViz/plot/templates/diurnal_pattern.py +28 -26
  46. AeroViz/plot/templates/koschmieder.py +59 -123
  47. AeroViz/plot/templates/metal_heatmap.py +135 -37
  48. AeroViz/plot/timeseries/__init__.py +1 -0
  49. AeroViz/plot/timeseries/template.py +47 -0
  50. AeroViz/plot/timeseries/timeseries.py +324 -264
  51. AeroViz/plot/utils/__init__.py +2 -1
  52. AeroViz/plot/utils/_color.py +57 -57
  53. AeroViz/plot/utils/_unit.py +48 -48
  54. AeroViz/plot/utils/plt_utils.py +92 -0
  55. AeroViz/plot/utils/sklearn_utils.py +49 -0
  56. AeroViz/plot/utils/units.json +5 -0
  57. AeroViz/plot/violin.py +80 -0
  58. AeroViz/process/__init__.py +17 -17
  59. AeroViz/process/core/DataProc.py +9 -9
  60. AeroViz/process/core/SizeDist.py +81 -81
  61. AeroViz/process/method/PyMieScatt_update.py +488 -488
  62. AeroViz/process/method/mie_theory.py +231 -229
  63. AeroViz/process/method/prop.py +40 -40
  64. AeroViz/process/script/AbstractDistCalc.py +103 -103
  65. AeroViz/process/script/Chemical.py +168 -167
  66. AeroViz/process/script/IMPACT.py +40 -40
  67. AeroViz/process/script/IMPROVE.py +152 -152
  68. AeroViz/process/script/Others.py +45 -45
  69. AeroViz/process/script/PSD.py +26 -26
  70. AeroViz/process/script/PSD_dry.py +69 -70
  71. AeroViz/process/script/retrieve_RI.py +50 -51
  72. AeroViz/rawDataReader/__init__.py +53 -58
  73. AeroViz/rawDataReader/config/supported_instruments.py +155 -0
  74. AeroViz/rawDataReader/core/__init__.py +233 -356
  75. AeroViz/rawDataReader/script/AE33.py +17 -18
  76. AeroViz/rawDataReader/script/AE43.py +18 -21
  77. AeroViz/rawDataReader/script/APS_3321.py +30 -30
  78. AeroViz/rawDataReader/script/Aurora.py +23 -24
  79. AeroViz/rawDataReader/script/BC1054.py +36 -40
  80. AeroViz/rawDataReader/script/EPA_vertical.py +37 -9
  81. AeroViz/rawDataReader/script/GRIMM.py +16 -23
  82. AeroViz/rawDataReader/script/IGAC.py +90 -0
  83. AeroViz/rawDataReader/script/MA350.py +32 -39
  84. AeroViz/rawDataReader/script/Minion.py +103 -0
  85. AeroViz/rawDataReader/script/NEPH.py +69 -74
  86. AeroViz/rawDataReader/script/SMPS_TH.py +25 -25
  87. AeroViz/rawDataReader/script/SMPS_aim11.py +32 -32
  88. AeroViz/rawDataReader/script/SMPS_genr.py +31 -31
  89. AeroViz/rawDataReader/script/Sunset_OCEC.py +60 -0
  90. AeroViz/rawDataReader/script/TEOM.py +30 -28
  91. AeroViz/rawDataReader/script/Table.py +13 -14
  92. AeroViz/rawDataReader/script/VOC.py +26 -0
  93. AeroViz/rawDataReader/script/__init__.py +18 -20
  94. AeroViz/tools/database.py +64 -66
  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.3.dist-info → AeroViz-0.1.4.dist-info}/METADATA +5 -4
  99. AeroViz-0.1.4.dist-info/RECORD +112 -0
  100. AeroViz/plot/improve/__init__.py +0 -1
  101. AeroViz/plot/improve/improve.py +0 -240
  102. AeroViz/plot/optical/aethalometer.py +0 -77
  103. AeroViz/plot/templates/event_evolution.py +0 -65
  104. AeroViz/plot/templates/regression.py +0 -256
  105. AeroViz/plot/templates/scatter.py +0 -130
  106. AeroViz/plot/templates/templates.py +0 -398
  107. AeroViz/plot/utils/_decorator.py +0 -74
  108. AeroViz/rawDataReader/script/IGAC_TH.py +0 -104
  109. AeroViz/rawDataReader/script/IGAC_ZM.py +0 -90
  110. AeroViz/rawDataReader/script/OCEC_LCRES.py +0 -34
  111. AeroViz/rawDataReader/script/OCEC_RES.py +0 -28
  112. AeroViz/rawDataReader/script/VOC_TH.py +0 -30
  113. AeroViz/rawDataReader/script/VOC_ZM.py +0 -37
  114. AeroViz/rawDataReader/utils/__init__.py +0 -0
  115. AeroViz/rawDataReader/utils/config.py +0 -169
  116. AeroViz-0.1.3.dist-info/RECORD +0 -111
  117. /AeroViz/{config → data}/DEFAULT_PNSD_DATA.csv +0 -0
  118. /AeroViz/{config → rawDataReader/config}/__init__.py +0 -0
  119. {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/LICENSE +0 -0
  120. {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/WHEEL +0 -0
  121. {AeroViz-0.1.3.dist-info → AeroViz-0.1.4.dist-info}/top_level.txt +0 -0
AeroViz/plot/pie.py ADDED
@@ -0,0 +1,198 @@
1
+ from typing import Literal
2
+
3
+ import matplotlib.pyplot as plt
4
+ import numpy as np
5
+ import pandas as pd
6
+ from matplotlib.pyplot import Figure, Axes
7
+ from pandas import DataFrame
8
+
9
+ from AeroViz.plot.utils import *
10
+
11
+ __all__ = [
12
+ 'pie',
13
+ 'donuts'
14
+ ]
15
+
16
+
17
+ @set_figure(fw='bold')
18
+ def pie(data_set: DataFrame | dict,
19
+ labels: list[str],
20
+ unit: str,
21
+ style: Literal["pie", 'donut'],
22
+ ax: Axes | None = None,
23
+ symbol: bool = True,
24
+ **kwargs) -> tuple[Figure, Axes]:
25
+ """
26
+ Create a pie or donut chart based on the provided data.
27
+
28
+ Parameters
29
+ ----------
30
+ data_set : pd.DataFrame | dict
31
+ A pandas DataFrame or dictionary mapping category names to a list of species.
32
+ If a DataFrame is provided, the index represents the categories, and each column contains species data.
33
+ If a dictionary is provided, it maps category names to lists of species data.
34
+ It is assumed that all lists or DataFrame columns contain the same number of entries as the *labels* list.
35
+ labels : list of str
36
+ The labels for each category.
37
+ unit : str
38
+ The unit to display in the center of the donut chart.
39
+ style : Literal["pie", 'donut']
40
+ The style of the chart, either 'pie' for a standard pie chart or 'donut' for a donut chart.
41
+ ax : plt.Axes or None, optional
42
+ The Axes object to plot the chart onto. If None, a new figure and Axes will be created.
43
+ symbol : bool, optional
44
+ Whether to display values for each species in the chart.
45
+ **kwargs
46
+ Additional keyword arguments to be passed to the plotting function.
47
+
48
+ Returns
49
+ -------
50
+ matplotlib.axes.Axes
51
+ The Axes object containing the violin plot.
52
+
53
+ Notes
54
+ -----
55
+ - If *data_set* is a dictionary, it should contain lists of species that correspond to each category in *labels*.
56
+ - The length of each list in *data_set* or the number of columns in the DataFrame should match the length of the *labels* list.
57
+
58
+ Examples
59
+ --------
60
+ >>> data_set = {'Category 1': [10, 20, 30], 'Category 2': [15, 25, 35]}
61
+ >>> labels = ['Species 1', 'Species 2', 'Species 3']
62
+ >>> pie(data_set, labels, unit='kg', style='pie', symbol=True)
63
+ """
64
+ if isinstance(data_set, DataFrame):
65
+ category_names = list(data_set.index)
66
+ data = data_set.to_numpy()
67
+
68
+ pies, species = data.shape
69
+
70
+ elif isinstance(data_set, dict):
71
+ category_names = list(data_set.keys())
72
+ data = np.array(list(data_set.values()))
73
+
74
+ pies, species = data.shape
75
+
76
+ else:
77
+ raise ValueError('data_set must be a DataFrame or a dictionary.')
78
+
79
+ colors = kwargs.get('colors') or (Color.colors1 if species == 6 else Color.getColor(num=species))
80
+
81
+ radius = 4
82
+ width = 4 if style == 'pie' else 1
83
+
84
+ text = [''] * pies if style == 'pie' else [Unit(unit) + '\n\n' + '{:.2f}'.format(x) for x in data.sum(axis=1)]
85
+ pct_distance = 0.6 if style == 'pie' else 0.88
86
+
87
+ fig, ax = plt.subplots(1, pies, figsize=((pies * 2) + 1, 2)) if ax is None else (ax.get_figure(), ax)
88
+
89
+ if pies == 1:
90
+ ax = [ax]
91
+
92
+ for i in range(pies):
93
+ ax[i].pie(data[i], labels=None, colors=colors, textprops=None,
94
+ autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
95
+ pctdistance=pct_distance, radius=radius, wedgeprops=dict(width=width, edgecolor='w'))
96
+
97
+ ax[i].pie(data[i], labels=None, colors=colors, textprops=None,
98
+ autopct=lambda pct: auto_label_pct(pct, symbol=symbol, ignore='outer', include_pct=True),
99
+ pctdistance=1.3, radius=radius, wedgeprops=dict(width=width, edgecolor='w'))
100
+ ax[i].axis('equal')
101
+ ax[i].text(0, 0, text[i], ha='center', va='center')
102
+ ax[i].set_title(category_names[i])
103
+
104
+ ax[-1].legend(labels, loc='center left', prop={'size': 8, 'weight': 'normal'}, bbox_to_anchor=(1, 0, 1.15, 1))
105
+
106
+ # fig.savefig(f"pie_{style}_{title}")
107
+
108
+ plt.show()
109
+
110
+ return fig, ax
111
+
112
+
113
+ @set_figure(fw='bold')
114
+ def donuts(data_set: DataFrame | dict,
115
+ labels: list[str],
116
+ unit: str,
117
+ ax: Axes | None = None,
118
+ symbol=True,
119
+ **kwargs) -> tuple[Figure, Axes]:
120
+ """
121
+ Plot a donut chart based on the data set.
122
+
123
+ Parameters
124
+ ----------
125
+ data_set : pd.DataFrame | dict
126
+ A pandas DataFrame or a dictionary mapping category names to a list of species.
127
+ If a DataFrame is provided, the index represents the categories, and each column contains species data.
128
+ If a dictionary is provided, it maps category names to lists of species data.
129
+ It is assumed that all lists or DataFrame columns contain the same number of entries as the *labels* list.
130
+ labels : list of str
131
+ The category labels.
132
+ unit : str
133
+ The unit to be displayed in the center of the donut chart.
134
+ ax : matplotlib.axes.Axes, optional
135
+ The axes to plot on. If None, the current axes will be used (default).
136
+ symbol : bool, optional
137
+ Whether to display values for each species (default is True).
138
+ **kwargs : dict, optional
139
+ Additional keyword arguments to pass to the matplotlib pie chart function.
140
+
141
+ Returns
142
+ -------
143
+ matplotlib.axes.Axes
144
+ The axes containing the donut chart.
145
+ """
146
+
147
+ if isinstance(data_set, DataFrame):
148
+ category_names = list(data_set.index)
149
+ data = data_set.to_numpy()
150
+
151
+ pies, species = data.shape
152
+
153
+ elif isinstance(data_set, dict):
154
+ category_names = list(data_set.keys())
155
+ data = np.array(list(data_set.values()))
156
+
157
+ pies, species = data.shape
158
+
159
+ else:
160
+ raise ValueError('data_set must be a DataFrame or a dictionary.')
161
+
162
+ colors1 = kwargs.get('colors') or (Color.colors1 if species == 6 else Color.getColor(num=species))
163
+ colors2 = Color.adjust_opacity(colors1, 0.8)
164
+ colors3 = Color.adjust_opacity(colors1, 0.6)
165
+
166
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
167
+
168
+ ax.pie(data[2], labels=None, colors=colors1, textprops=None,
169
+ autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
170
+ pctdistance=0.9, radius=14, wedgeprops=dict(width=3, edgecolor='w'))
171
+
172
+ ax.pie(data[1], labels=None, colors=colors2, textprops=None,
173
+ autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
174
+ pctdistance=0.85, radius=11, wedgeprops=dict(width=3, edgecolor='w'))
175
+
176
+ ax.pie(data[0], labels=None, colors=colors3, textprops=None,
177
+ autopct=lambda pct: auto_label_pct(pct, symbol=symbol, include_pct=True),
178
+ pctdistance=0.80, radius=8, wedgeprops=dict(width=3, edgecolor='w'))
179
+
180
+ text = (Unit(f'{unit}') + '\n\n' +
181
+ 'Event : ' + "{:.2f}".format(np.sum(data[2])) + '\n' +
182
+ 'Transition : ' + "{:.2f}".format(np.sum(data[1])) + '\n' +
183
+ 'Clean : ' + "{:.2f}".format(np.sum(data[0])))
184
+
185
+ ax.text(0, 0, text, ha='center', va='center')
186
+ ax.axis('equal')
187
+
188
+ ax.set_title(kwargs.get('title', ''))
189
+
190
+ ax.legend(labels, loc='center', prop={'size': 8}, title_fontproperties={'weight': 'bold'},
191
+ title=f'Outer : {category_names[2]}' + '\n' + f'Middle : {category_names[1]}' + '\n' + f'Inner : {category_names[0]}',
192
+ bbox_to_anchor=(0.8, 0, 0.5, 1))
193
+
194
+ # fig.savefig(f"donuts_{title}")
195
+
196
+ plt.show()
197
+
198
+ return fig, ax
@@ -0,0 +1,196 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ import pandas as pd
4
+ from matplotlib.pyplot import Figure, Axes
5
+
6
+ from AeroViz.plot.utils import *
7
+
8
+ __all__ = [
9
+ 'linear_regression',
10
+ 'multiple_linear_regression',
11
+ ]
12
+
13
+
14
+ @set_figure
15
+ def linear_regression(df: pd.DataFrame,
16
+ x: str | list[str],
17
+ y: str | list[str],
18
+ labels: str | list[str] = None,
19
+ ax: Axes | None = None,
20
+ diagonal=False,
21
+ positive: bool = True,
22
+ fit_intercept: bool = True,
23
+ **kwargs
24
+ ) -> tuple[Figure, Axes]:
25
+ """
26
+ Create a scatter plot with regression lines for the given data.
27
+
28
+ Parameters
29
+ ----------
30
+ df : pd.DataFrame
31
+ Input DataFrame containing the data.
32
+ x : str or list of str
33
+ Column name(s) for the x-axis variable(s). If a list, only the first element is used.
34
+ y : str or list of str
35
+ Column name(s) for the y-axis variable(s).
36
+ labels : str or list of str, optional
37
+ Labels for the y-axis variable(s). If None, column names are used as labels. Default is None.
38
+ ax : Axes, optional
39
+ Matplotlib Axes object to use for the plot. If None, a new subplot is created. Default is None.
40
+ diagonal : bool, optional
41
+ If True, a diagonal line (1:1 line) is added to the plot. Default is False.
42
+ positive : bool, optional
43
+ Whether to constrain the regression coefficients to be positive. Default is True.
44
+ fit_intercept: bool, optional
45
+ Whether to calculate the intercept for this model. Default is True.
46
+ **kwargs
47
+ Additional keyword arguments for plot customization.
48
+
49
+ Returns
50
+ -------
51
+ fig : Figure
52
+ The matplotlib Figure object.
53
+ ax : Axes
54
+ The matplotlib Axes object with the scatter plot.
55
+
56
+ Notes
57
+ -----
58
+ - The function creates a scatter plot with optional regression lines.
59
+ - The regression line is fitted for each y variable.
60
+ - Customization options are provided via **kwargs.
61
+
62
+ Example
63
+ -------
64
+ >>> linear_regression(df, x='X', y=['Y1', 'Y2'], labels=['Label1', 'Label2'],
65
+ ... diagonal=True, xlim=(0, 10), ylim=(0, 20),
66
+ ... xlabel="X-axis", ylabel="Y-axis", title="Scatter Plot with Regressions")
67
+ """
68
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
69
+
70
+ if not isinstance(x, str):
71
+ x = x[0]
72
+
73
+ if not isinstance(y, list):
74
+ y = [y]
75
+
76
+ if labels is None:
77
+ labels = y
78
+
79
+ df = df.dropna(subset=[x, *y])
80
+ x_array = df[[x]].to_numpy()
81
+
82
+ color_cycle = Color.linecolor
83
+
84
+ handles, text_list = [], []
85
+
86
+ for i, y_var in enumerate(y):
87
+ y_array = df[[y_var]].to_numpy()
88
+
89
+ color = color_cycle[i % len(color_cycle)]
90
+
91
+ scatter = ax.scatter(x_array, y_array, s=25, color=color['face'], edgecolors=color['edge'], alpha=0.8,
92
+ label=labels[i])
93
+ handles.append(scatter)
94
+
95
+ text, y_predict, slope = linear_regression_base(x_array, y_array,
96
+ columns=labels[i],
97
+ positive=positive,
98
+ fit_intercept=fit_intercept)
99
+
100
+ text_list.append(f'{labels[i]}:\n{text}')
101
+ plt.plot(x_array, y_predict, linewidth=3, color=color['line'], alpha=1, zorder=3)
102
+
103
+ ax.set(xlim=kwargs.get('xlim'), ylim=kwargs.get('ylim'), xlabel=Unit(x), ylabel=Unit(y[0]),
104
+ title=kwargs.get('title'))
105
+
106
+ # Add regression info to the legend
107
+ leg = plt.legend(handles=handles, labels=text_list, loc='upper left', prop={'weight': 'bold'})
108
+
109
+ for text, color in zip(leg.get_texts(), [color['line'] for color in color_cycle]):
110
+ text.set_color(color)
111
+
112
+ if diagonal:
113
+ ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
114
+ plt.text(0.97, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
115
+
116
+ plt.show()
117
+
118
+ return fig, ax
119
+
120
+
121
+ @set_figure
122
+ def multiple_linear_regression(df: pd.DataFrame,
123
+ x: str | list[str],
124
+ y: str | list[str],
125
+ labels: str | list[str] = None,
126
+ ax: Axes | None = None,
127
+ diagonal=False,
128
+ positive: bool = True,
129
+ fit_intercept: bool = True,
130
+ **kwargs
131
+ ) -> tuple[Figure, Axes]:
132
+ """
133
+ Perform multiple linear regression analysis and plot the results.
134
+
135
+ Parameters
136
+ ----------
137
+ df : pd.DataFrame
138
+ Input DataFrame containing the data.
139
+ x : str or list of str
140
+ Column name(s) for the independent variable(s). Can be a single string or a list of strings.
141
+ y : str or list of str
142
+ Column name(s) for the dependent variable(s). Can be a single string or a list of strings.
143
+ labels : str or list of str, optional
144
+ Labels for the dependent variable(s). If None, column names are used as labels. Default is None.
145
+ ax : Axes, optional
146
+ Matplotlib Axes object to use for the plot. If None, a new subplot is created. Default is None.
147
+ diagonal : bool, optional
148
+ Whether to include a diagonal line (1:1 line) in the plot. Default is False.
149
+ positive : bool, optional
150
+ Whether to constrain the regression coefficients to be positive. Default is True.
151
+ fit_intercept: bool, optional
152
+ Whether to calculate the intercept for this model. Default is True.
153
+ **kwargs
154
+ Additional keyword arguments for plot customization.
155
+
156
+ Returns
157
+ -------
158
+ tuple[Figure, Axes]
159
+ The Figure and Axes containing the regression plot.
160
+
161
+ Notes
162
+ -----
163
+ This function performs multiple linear regression analysis using the input DataFrame.
164
+ It supports multiple independent variables and can plot the regression results.
165
+
166
+ Example
167
+ -------
168
+ >>> multiple_linear_regression(df, x=['X1', 'X2'], y='Y', labels=['Y1', 'Y2'],
169
+ ... diagonal=True, fit_intercept=True,
170
+ ... xlabel="X-axis", ylabel="Y-axis", title="Multiple Linear Regression Plot")
171
+ """
172
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
173
+
174
+ if not isinstance(x, list):
175
+ x = [x]
176
+
177
+ if not isinstance(y, str):
178
+ y = y[0]
179
+
180
+ if labels is None:
181
+ labels = x
182
+
183
+ df = df[[*x, y]].dropna()
184
+ x_array = df[[*x]].to_numpy()
185
+ y_array = df[[y]].to_numpy()
186
+
187
+ text, y_predict, coefficients = linear_regression_base(x_array, y_array,
188
+ columns=labels,
189
+ positive=positive,
190
+ fit_intercept=fit_intercept)
191
+
192
+ df = pd.DataFrame(np.concatenate([y_array, y_predict], axis=1), columns=['y_actual', 'y_predict'])
193
+
194
+ linear_regression(df, x='y_actual', y='y_predict', ax=ax, regression=True, diagonal=diagonal)
195
+
196
+ return fig, ax
@@ -0,0 +1,165 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ import pandas as pd
4
+ import seaborn as sns
5
+ from matplotlib.colors import Normalize
6
+ from matplotlib.pyplot import Figure, Axes
7
+ from matplotlib.ticker import ScalarFormatter
8
+
9
+ from AeroViz.plot.utils import *
10
+
11
+ __all__ = ['scatter']
12
+
13
+
14
+ def check_empty(*arrays):
15
+ for i, arr in enumerate(arrays):
16
+ if arr.size == 0:
17
+ raise ValueError(f"Array is empty!")
18
+
19
+
20
+ @set_figure
21
+ def scatter(df: pd.DataFrame,
22
+ x: str,
23
+ y: str,
24
+ c: str | None = None,
25
+ s: str | None = None,
26
+ cmap='jet',
27
+ regression=False,
28
+ diagonal=False,
29
+ ax: Axes | None = None,
30
+ **kwargs
31
+ ) -> tuple[Figure, Axes]:
32
+ """
33
+ Creates a scatter plot with optional color and size encoding.
34
+
35
+ Parameters
36
+ ----------
37
+ df : pd.DataFrame
38
+ The DataFrame containing the data to plot.
39
+ x : str
40
+ The column name for the x-axis values.
41
+ y : str
42
+ The column name for the y-axis values.
43
+ c : str, optional
44
+ The column name for color encoding. Default is None.
45
+ s : str, optional
46
+ The column name for size encoding. Default is None.
47
+ cmap : str, optional
48
+ The colormap to use for the color encoding. Default is 'jet'.
49
+ regression : bool, optional
50
+ If True, fits and plots a linear regression line. Default is False.
51
+ diagonal : bool, optional
52
+ If True, plots a 1:1 diagonal line. Default is False.
53
+ ax : Axes, optional
54
+ The matplotlib Axes to plot on. If not provided, a new figure and axes are created.
55
+ **kwargs : Any
56
+ Additional keyword arguments passed to customize the plot, such as `fig_kws` for figure creation and `xlabel`,
57
+ `ylabel`, `xlim`, `ylim`, `title` for axis labeling and limits.
58
+
59
+ Returns
60
+ -------
61
+ fig : Figure
62
+ The matplotlib Figure object.
63
+ ax : Axes
64
+ The matplotlib Axes object with the scatter plot.
65
+
66
+ Notes
67
+ -----
68
+ - If both `c` and `s` are provided, the scatter plot will encode data points using both color and size.
69
+ - If only `c` is provided, data points will be color-coded according to the values in the `c` column.
70
+ - If only `s` is provided, data points will be sized according to the values in the `s` column.
71
+ - If neither `c` nor `s` is provided, a basic scatter plot is created.
72
+ - The `regression` option will add a linear regression line and display the equation on the plot.
73
+ - The `diagonal` option will add a 1:1 reference line to the plot.
74
+
75
+ Examples
76
+ --------
77
+ >>> import pandas as pd
78
+ >>> from AeroViz.plot import scatter
79
+ >>> df = pd.DataFrame({
80
+ >>> 'x': [1, 2, 3, 4],
81
+ >>> 'y': [1.1, 2.0, 2.9, 4.1],
82
+ >>> 'color': [10, 20, 30, 40],
83
+ >>> 'size': [100, 200, 300, 400]
84
+ >>> })
85
+ >>> fig, ax = scatter(df, x='x', y='y', c='color', s='size', regression=True, diagonal=True)
86
+ """
87
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
88
+
89
+ if c is not None and s is not None:
90
+ df_ = df.dropna(subset=[x, y, c, s]).copy()
91
+ x_data, y_data, c_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[c].to_numpy(), df_[s].to_numpy()
92
+ check_empty(x_data, y_data, c_data, s_data)
93
+
94
+ scatter = ax.scatter(x_data, y_data, c=c_data,
95
+ norm=Normalize(vmin=np.percentile(c_data, 10), vmax=np.percentile(c_data, 90)),
96
+ cmap=cmap, s=50 * (s_data / s_data.max()) ** 1.5, alpha=0.7, edgecolors=None)
97
+ colorbar = True
98
+
99
+ dot = np.linspace(s_data.min(), s_data.max(), 6).round(-1)
100
+
101
+ for dott in dot[1:-1]:
102
+ plt.scatter([], [], c='k', alpha=0.8, s=50 * (dott / s_data.max()) ** 1.5, label='{:.0f}'.format(dott))
103
+
104
+ plt.legend(title=Unit(s))
105
+
106
+ elif c is not None:
107
+ df_ = df.dropna(subset=[x, y, c]).copy()
108
+ x_data, y_data, c_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[c].to_numpy()
109
+ check_empty(x_data, y_data, c_data)
110
+
111
+ scatter = ax.scatter(x_data, y_data, c=c_data, vmin=c_data.min(), vmax=np.percentile(c_data, 90), cmap=cmap,
112
+ alpha=0.7,
113
+ edgecolors=None)
114
+ colorbar = True
115
+
116
+ elif s is not None:
117
+ df_ = df.dropna(subset=[x, y, s]).copy()
118
+ x_data, y_data, s_data = df_[x].to_numpy(), df_[y].to_numpy(), df_[s].to_numpy()
119
+ check_empty(x_data, y_data, s_data)
120
+
121
+ scatter = ax.scatter(x_data, y_data, s=50 * (s_data / s_data.max()) ** 1.5, color='#7a97c9', alpha=0.7,
122
+ edgecolors='white')
123
+ colorbar = False
124
+
125
+ # dealing
126
+ dot = np.linspace(s_data.min(), s_data.max(), 6).round(-1)
127
+
128
+ for dott in dot[1:-1]:
129
+ plt.scatter([], [], c='k', alpha=0.8, s=50 * (dott / s_data.max()) ** 1.5, label='{:.0f}'.format(dott))
130
+
131
+ plt.legend(title=Unit(s))
132
+
133
+ else:
134
+ df_ = df.dropna(subset=[x, y]).copy()
135
+ x_data, y_data = df_[x].to_numpy(), df_[y].to_numpy()
136
+ check_empty(x_data, y_data)
137
+
138
+ scatter = ax.scatter(x_data, y_data, s=30, color='#7a97c9', alpha=0.7, edgecolors='white')
139
+ colorbar = False
140
+
141
+ ax.set(xlim=kwargs.get('xlim', (x_data.min(), x_data.max())),
142
+ ylim=kwargs.get('ylim', (y_data.min(), y_data.max())),
143
+ xlabel=kwargs.get('xlabel', Unit(x)),
144
+ ylabel=kwargs.get('ylabel', Unit(y)),
145
+ title=kwargs.get('title', ''))
146
+
147
+ if colorbar:
148
+ plt.colorbar(scatter, extend='both', label=Unit(c))
149
+
150
+ if regression:
151
+ text, y_predict, slope = linear_regression_base(x_data, y_data)
152
+ ax.plot(x_data, y_predict, linewidth=3, color=sns.xkcd_rgb["denim blue"], alpha=1, zorder=3)
153
+ plt.text(0.05, 0.95, text, fontdict={'weight': 'bold'}, color=sns.xkcd_rgb["denim blue"],
154
+ ha='left', va='top', transform=ax.transAxes)
155
+
156
+ if diagonal:
157
+ ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
158
+ plt.text(0.91, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
159
+
160
+ ax.xaxis.set_major_formatter(ScalarFormatter())
161
+ ax.yaxis.set_major_formatter(ScalarFormatter())
162
+
163
+ plt.show()
164
+
165
+ return fig, ax
@@ -1,8 +1,6 @@
1
+ from .ammonium_rich import ammonium_rich
1
2
  from .contour import *
2
3
  from .corr_matrix import corr_matrix
3
4
  from .diurnal_pattern import *
4
5
  from .koschmieder import *
5
- from .metal_heatmap import metal_heatmaps, process_data
6
- from .regression import *
7
- from .scatter import *
8
- from .templates import *
6
+ from .metal_heatmap import metal_heatmaps, process_data_with_two_df
@@ -0,0 +1,34 @@
1
+ import matplotlib.pyplot as plt
2
+ from matplotlib.pyplot import Figure, Axes
3
+ from pandas import DataFrame
4
+
5
+ from AeroViz.plot.utils import set_figure, Unit
6
+
7
+
8
+ @set_figure(figsize=(5, 4))
9
+ def ammonium_rich(df: DataFrame,
10
+ **kwargs
11
+ ) -> tuple[Figure, Axes]:
12
+ df = df[['NH4+', 'SO42-', 'NO3-', 'PM2.5']].dropna().copy().div([18, 96, 62, 1])
13
+ df['required_ammonium'] = df['NO3-'] + 2 * df['SO42-']
14
+
15
+ fig, ax = plt.subplots()
16
+
17
+ scatter = ax.scatter(df['required_ammonium'].to_numpy(), df['NH4+'].to_numpy(), c=df['PM2.5'].to_numpy(),
18
+ vmin=0, vmax=70, cmap='jet', marker='o', s=10, alpha=1)
19
+
20
+ ax.axline((0, 0), slope=1., color='k', lw=2, ls='--', alpha=0.5, label='1:1')
21
+ plt.text(0.97, 0.97, r'$\bf 1:1\ Line$', color='k', ha='right', va='top', transform=ax.transAxes)
22
+
23
+ ax.set(xlim=(0, 1.2),
24
+ ylim=(0, 1.2),
25
+ xlabel=r'$\bf NO_{3}^{-}\ +\ 2\ \times\ SO_{4}^{2-}\ (mole\ m^{-3})$',
26
+ ylabel=r'$\bf NH_{4}^{+}\ (mole\ m^{-3})$',
27
+ title=kwargs.get('title', ''))
28
+
29
+ color_bar = plt.colorbar(scatter, label=Unit('PM2.5'), extend='both')
30
+
31
+ # fig.savefig(f'Ammonium_rich_{title}')
32
+ plt.show()
33
+
34
+ return fig, ax
@@ -10,38 +10,38 @@ __all__ = ['contour']
10
10
 
11
11
  @set_figure
12
12
  def contour(df, ax: Axes | None = None, **kwargs) -> tuple[Figure, Axes]:
13
- fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
13
+ fig, ax = plt.subplots(**kwargs.get('fig_kws', {})) if ax is None else (ax.get_figure(), ax)
14
14
 
15
- npoints = 1000
16
- xreg = np.linspace(df.PM25.min(), df.PM25.max(), 83)
17
- yreg = np.linspace(df.gRH.min(), df.gRH.max(), 34)
18
- X, Y = np.meshgrid(xreg, yreg)
15
+ npoints = 1000
16
+ xreg = np.linspace(df.PM25.min(), df.PM25.max(), 83)
17
+ yreg = np.linspace(df.gRH.min(), df.gRH.max(), 34)
18
+ X, Y = np.meshgrid(xreg, yreg)
19
19
 
20
- d_f = df.copy()
21
- df['gRH'] = d_f['gRH'].round(2)
22
- df['PM25'] = d_f['PM25'].round(2)
20
+ d_f = df.copy()
21
+ df['gRH'] = d_f['gRH'].round(2)
22
+ df['PM25'] = d_f['PM25'].round(2)
23
23
 
24
- def func(data, *params):
25
- return params[0] * data ** (params[1])
24
+ def func(data, *params):
25
+ return params[0] * data ** (params[1])
26
26
 
27
- initial_guess = [1.0, 1.0]
27
+ initial_guess = [1.0, 1.0]
28
28
 
29
- fit_df = df[['PM25', 'gRH', 'Extinction']].dropna()
30
- popt, pcov = curve_fit(func, xdata=(fit_df['PM25'] * fit_df['gRH']), ydata=fit_df['Extinction'], p0=initial_guess,
31
- maxfev=2000000, method='trf')
29
+ fit_df = df[['PM25', 'gRH', 'Extinction']].dropna()
30
+ popt, pcov = curve_fit(func, xdata=(fit_df['PM25'] * fit_df['gRH']), ydata=fit_df['Extinction'], p0=initial_guess,
31
+ maxfev=2000000, method='trf')
32
32
 
33
- x, y = df.PM25, df.gRH
33
+ x, y = df.PM25, df.gRH
34
34
 
35
- # pcolor = ax.pcolormesh(X, Y, (X * 4.5 * Y ** (1 / 3)), cmap='jet', shading='auto', vmin=0, vmax=843, alpha=0.8)
36
- Z = func(X * Y, *popt)
37
- cont = ax.contour(X, Y, Z, colors='black', levels=5, vmin=0, vmax=Z.max())
38
- conf = ax.contourf(X, Y, Z, cmap='YlGnBu', levels=100, vmin=0, vmax=Z.max())
39
- ax.clabel(cont, colors=['black'], fmt=lambda s: f"{s:.0f} 1/Mm")
40
- ax.set(xlabel=Unit('PM25'), ylabel=Unit('gRH'), xlim=(x.min(), x.max()), ylim=(y.min(), y.max()))
35
+ # pcolor = ax.pcolormesh(X, Y, (X * 4.5 * Y ** (1 / 3)), cmap='jet', shading='auto', vmin=0, vmax=843, alpha=0.8)
36
+ Z = func(X * Y, *popt)
37
+ cont = ax.contour(X, Y, Z, colors='black', levels=5, vmin=0, vmax=Z.max())
38
+ conf = ax.contourf(X, Y, Z, cmap='YlGnBu', levels=100, vmin=0, vmax=Z.max())
39
+ ax.clabel(cont, colors=['black'], fmt=lambda s: f"{s:.0f} 1/Mm")
40
+ ax.set(xlabel=Unit('PM25'), ylabel=Unit('gRH'), xlim=(x.min(), x.max()), ylim=(y.min(), y.max()))
41
41
 
42
- color_bar = plt.colorbar(conf, pad=0.02, fraction=0.05, label='Extinction (1/Mm)')
43
- color_bar.ax.set_xticklabels(color_bar.ax.get_xticks().astype(int))
42
+ color_bar = plt.colorbar(conf, pad=0.02, fraction=0.05, label='Extinction (1/Mm)')
43
+ color_bar.ax.set_xticklabels(color_bar.ax.get_xticks().astype(int))
44
44
 
45
- plt.show()
45
+ plt.show()
46
46
 
47
- return fig, ax
47
+ return fig, ax